Auto-approve cheap CI runs of external contributors' PRs (#75455)

* Auto-approve cheap CI runs of external contributors' PRs

GitHub holds the workflow runs of fork PRs until a maintainer approves
them. With self-hosted runners (AIP-118) the approval gate becomes part
of the trust model, so it should cover all external contributors, but
approving every push by hand does not scale and makes contributors wait.

Runs whose change cannot touch the CI definition and whose CI is cheap
are safe to start without a maintainer. Expensive or large changes, and
anything that changes how CI or runners are chosen, still get a human
look first, which also keeps the load on the shared public runners down.
The CI-minutes estimate lets maintainers see what a run will cost before
approving it.

Generated-by: Claude Opus 5

* Add inline script metadata to the CI-minutes calibration script

The script uses only the standard library, so with a PEP 723 block it runs
with plain `uv run dev/calibrate_ci_minutes_estimates.py` instead of syncing
the whole Breeze environment. Document that invocation in the script and in
.github/AGENTS.md.

* Keep one PR's API or git error from aborting the approval tick

Each PR is now processed in its own try block. A requests.RequestException
or subprocess.CalledProcessError for one PR no longer stops the remaining
PRs or skips the job summary. The failed PR is recorded with failed=True and
shown as "failed" in the summary, keeping its CI-time estimate when one was
computed and giving a short, single-line form of the error as the reason.
The workflow also tries to add the 'need workflow approval' label to it, so
it is not lost once its runs leave the query window. Nothing is approved on
failure.

approve_workflow_runs exits 1 after writing the summary when any PR failed,
so a persistent failure turns the scheduled run red instead of passing
silently.

* Approve the runs of fork CI pull requests unless they change CI

A pull request whose CI runs in the contributor's fork (AIP-120) still
gets pull_request runs in apache/airflow, and GitHub holds them until a
maintainer approves them. Those runs only execute the fork CI gate, so
judging them by the cost and size of project CI left cheap gate-only
runs waiting for a maintainer for no benefit, and a gated run that never
ran could not be cleaned up by the reconciler.

What still needs a maintainer is a change to the files that define CI,
since it could remove the gate or add a workflow that does run. Using
the reconciler's own list keeps the two decisions consistent, and the
author is told why their runs wait instead of only seeing a label.

Generated-by: Claude Opus 5

* Keep CI-minutes estimates true to the runs they approve

A run selects its tests from the labels the PR had when it started, so
approving it on the current labels could start a full-tests run whose
label was removed while it waited. Calibrating from self-hosted and
all-versions runs, and leaving out the jobs that need no CI image, made
the estimate describe runs other than those it is used to approve.

Generated-by: Claude Opus 5

---------

Co-authored-by: Shahar Epstein <60007259+shahar1@users.noreply.github.com>
This commit is contained in:
Jarek PotiukandShahar Epstein authored and GitHub committed 2026-10-25 20:14:06 +00:00
1 parent 47296ef589
commit 2a3ee7ffe2
25 files changed
+3332 -114

No files matched your search

+38
View File
@@ -0,0 +1,38 @@
<!-- SPDX-License-Identifier: Apache-2.0
https://www.apache.org/licenses/LICENSE-2.0 -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [The `.github` folder — Agent Instructions](#the-github-folder--agent-instructions)
- [Keep the CI-minutes estimates in sync with the workflows](#keep-the-ci-minutes-estimates-in-sync-with-the-workflows)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# The `.github` folder — Agent Instructions
## Keep the CI-minutes estimates in sync with the workflows
`breeze ci approve-workflow-runs` (run by `workflows/auto-approve-workflow-runs.yml`) decides whether
to approve an external contributor's CI runs from an estimate of the job-minutes they take. The
numbers live in `dev/breeze/src/airflow_breeze/utils/ci_minutes_estimate.py` and are medians
measured on recent `Tests (AMD)` PR runs on public runners.
After you change the CI workflows in a way that adds, removes, splits or resizes jobs (new test
groups, a changed matrix, new composite workflows, a move between runner types), refresh the
estimates once the change has run on enough PRs, and in any case about once a month:
1. Run `uv run dev/calibrate_ci_minutes_estimates.py --since <date>`
from the repository root with an authenticated `gh` CLI. Pick a date that covers at least a
week of runs after the workflow change.
2. Copy the medians and fits it prints into the constants of `ci_minutes_estimate.py`, and update
the "Last calibrated" line next to them.
3. When you add a new job group to the workflows, add a matching group to both
`ci_minutes_estimate.py` (with the selective-check output that turns it on) and the
`JOB_GROUPS` patterns of the calibration script. Unmatched jobs fall into the base group.
4. Run `uv run --project dev/breeze pytest dev/breeze/tests/test_ci_minutes_estimate.py
dev/breeze/tests/test_workflow_run_approval.py -xvs`.
The estimates cover public runners only. Self-hosted (RunsOn) runners need their own set once there
is enough data from them.
@@ -0,0 +1,68 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
---
# Approves the waiting workflow runs of external contributors' PRs to main when the change is safe to
# run without a maintainer's look, and labels the others "need workflow approval". A PR whose CI runs
# in the contributor's fork (AIP-120) is approved unless it changes CI definition files, and then gets
# a comment saying why its runs wait. The rules are in
# dev/breeze/src/airflow_breeze/utils/workflow_run_approval.py and documented in
# contributing-docs/05_pull_requests.rst.
#
# This workflow runs only trusted code from main. It reads the PR's metadata, changed-file list and
# git objects, and never checks out or runs the PR's code. Approving with GITHUB_TOKEN starts the
# approved runs right away, and workflow_run workflows that follow them still trigger.
name: Auto approve workflow runs
on: # yamllint disable-line rule:truthy
schedule:
- cron: '*/30 * * * *'
workflow_dispatch:
inputs:
report-only:
description: "Only report the decisions and estimates, without approving or labelling"
type: boolean
default: false
permissions:
# Approving workflow runs.
actions: write
# Adding and removing the "need workflow approval" label, and commenting on a fork CI PR whose
# CI change keeps its runs waiting.
pull-requests: write
contents: read
concurrency:
group: auto-approve-workflow-runs
cancel-in-progress: false
jobs:
approve-workflow-runs:
name: "Approve workflow runs of external contributors"
runs-on: ubuntu-latest
if: github.repository == 'apache/airflow'
timeout-minutes: 20
steps:
- name: "Checkout main"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
ref: main
- name: "Install Breeze"
uses: ./.github/actions/breeze
- name: "Approve workflow runs"
env:
GITHUB_TOKEN: ${{ github.token }}
GITHUB_REPOSITORY: ${{ github.repository }}
REPORT_ONLY: ${{ inputs.report-only && 'true' || 'false' }}
run: breeze ci approve-workflow-runs
+41
View File
@@ -242,6 +242,47 @@ is the PMC private list — see the
``private@airflow.apache.org``. That document also covers the further steps the project may
take when the same behaviour persists across PRs.
CI workflow runs of external contributors
-----------------------------------------
GitHub does not start the CI workflows of a PR from a fork until a maintainer approves them,
for the contributors that the repository's approval setting covers. To keep you from waiting
for a maintainer on every push, a scheduled workflow checks the waiting runs every 30 minutes
and approves them automatically when all of these hold:
* The PR targets ``main``. PRs to release branches, such as ``v3-4-test``, always wait for a
maintainer.
* The PR does not change anything under ``.github/``, ``dev/breeze/`` or ``scripts/ci/``. Those
files define the CI workflows and choose the runners they use.
* The change does not need the full set of tests, does not change dependencies (``pyproject.toml``,
lock files, ``package.json``, ``go.mod``, Gradle build files) and does not add a new provider.
* The CI jobs your change selects are estimated to take at most 310 job-minutes, about a quarter of
a full CI run. Changes to documentation, to the UI, to the Helm chart, to the language SDKs, to
one area of Airflow core or to a small provider usually stay under it. Changes to large providers
such as Amazon or Google, to several providers, or to the Task SDK (which runs the tests of all
providers) usually do not.
* The PR changes at most 100 lines of source code. Tests (including end-to-end tests),
documentation, newsfragments, translations and lock files are not counted, so a fix with
thorough tests still qualifies. Larger changes are usually new features, and a maintainer looks
at them first.
If your PR's CI runs in your fork (it carries the ``fork ci required`` label), the CI run in
``apache/airflow`` only checks that, so none of the rules above apply. Its runs are approved
automatically unless the PR changes files that define CI: anything under ``.github/``,
``dev/breeze/``, ``scripts/ci/``, ``scripts/docker/`` or ``scripts/in_container/``, a ``Dockerfile``
or ``.pre-commit-config.yaml``. Then the runs wait for a maintainer and a comment on the PR explains
why. CI in your fork runs either way.
Each new push is checked again, because approval applies to one commit. Only runs started in the
last 24 hours are considered, so older waiting runs stay with maintainers. If your PR does not
qualify, it gets the ``need workflow approval`` label and a maintainer approves the runs after
looking at the change. That can take a while, so please do not ping maintainers about it; it is
not a judgement of your PR. The label is removed when a later commit is approved automatically.
Maintainers can see each decision, the CI jobs selective checks expect to run and their estimated
time in the summary of the ``Auto approve workflow runs`` workflow. The rules are described in
more detail in `CI workflows <../dev/breeze/doc/ci/05_workflows.md>`__.
Gen-AI Assisted contributions
-----------------------------
@@ -238,6 +238,7 @@ The triage workflow uses the following labels and states:
| Label / State | Meaning |
|---|---|
| `ready for maintainer review` | PR has passed Stage 1 and is queued for human review. Applied only after the skill has verified that no workflow runs are awaiting approval. |
| `need workflow approval` | The PR's CI workflow runs wait for a maintainer, because the `Auto approve workflow runs` workflow did not approve them: a PR to a branch other than `main`, a change to CI definition files, a full-tests or dependency change, a change whose CI jobs are estimated at more than 310 job-minutes, or a change of more than 100 lines of source code (see [Pull Requests](05_pull_requests.rst)). For a PR whose CI runs in the contributor's fork, only a change to CI definition files keeps its runs waiting, and the workflow comments on the PR to say so. The workflow also tries to add the label when processing the PR failed; its run then fails too. Approve the runs after looking at the diff. The workflow removes the label when a later commit is approved automatically. |
| `closed because of multiple quality violations` | PR was closed because the author has multiple open PRs with quality issues. |
| `suspicious changes detected` | PR (and all open PRs by the same author) was closed because the diff contained suspicious patterns (secret exfiltration, malicious CI modifications, etc.). |
| `closed because of open PR limit` | PR was closed in the one-time closure that introduced the [open PR limit](32_open_pull_request_limit.rst). Applied by `dev/close_prs_over_open_pr_limit.py`, never by the triage skill. If the author reopens the PR, triage it like any other. |
+20
View File
@@ -200,6 +200,26 @@ These are all available flags of ``fork-ci-reconcile`` command:
:width: 100%
:alt: Breeze ci fork-ci-reconcile
Approving workflow runs of external contributors
------------------------------------------------
The ``auto-approve-workflow-runs`` workflow runs ``approve-workflow-runs`` every 30 minutes. It approves
the waiting workflow runs of external contributors' PRs to ``main`` whose CI jobs are estimated at no more
than 310 job-minutes, that change at most 100 lines of source code and that do not touch CI definition files, and labels the other PRs ``need workflow approval``. PRs whose CI runs in
the contributor's fork (AIP-120) only run the fork CI gate in ``apache/airflow``, so their runs are
approved whatever their size unless they change CI definition files; such a PR also gets a comment
explaining that its CI change needs a maintainer. For every PR it also
estimates the CI jobs selective checks will run and how long they take, and writes them to the job
summary. Use ``--report-only`` to see the decisions without approving runs or changing labels. The rules
are described in `CI workflows <ci/05_workflows.md>`_.
These are all available flags of ``approve-workflow-runs`` command:
.. image:: ./images/output_ci_approve-workflow-runs.svg
:target: https://raw.githubusercontent.com/apache/airflow/main/dev/breeze/images/output_ci_approve-workflow-runs.svg
:width: 100%
:alt: Breeze ci approve-workflow-runs
-----
Next step: Follow the `Release management tasks <09_release_management_tasks.rst>`_ guide to learn how
+62
View File
@@ -98,6 +98,68 @@ whose CI runs in the contributor's fork (AIP-120), the gate skips `build-info` a
everything after it, so the run finishes in seconds; see
[Fork CI for external contributors](08_fork_ci_for_external_contributors.md).
### Approval of pull request runs
GitHub holds the runs of a fork PR in `action_required` until someone with write access approves
them, for the contributors the repository's approval setting covers. The
`auto-approve-workflow-runs.yml` workflow runs every 30 minutes on `main` and calls
`breeze ci approve-workflow-runs`, which approves the waiting runs of a commit when all of these hold:
- the PR targets `main`; PRs to release branches (`v3-4-test`, `airflow-ctl/v0-1-test`, ...) run those
branches' own workflows and always wait for a maintainer
- the PR changes nothing under `.github/`, `dev/breeze/` or `scripts/ci/`, which define the workflows
and choose the runners
- the PR changes no dependency manifest or lock file (`pyproject.toml`, `uv.lock`, `package.json`,
`pnpm-lock.yaml`, `go.mod`, Gradle build files, ...) and adds no new provider (selective checks
run from `main`'s tree and do not know it)
- selective checks for the PR's merge commit succeed, and do not need the full set of tests, all Python
and database versions, a canary build or upgraded dependencies (the estimate does not model them)
- the jobs selective checks select are estimated at no more than 310 job-minutes on public runners
(about 23% of a full-tests run, just above the cluster of single core areas and the Helm chart)
- the PR changes at most 100 lines of source code; tests (including e2e tests), docs, newsfragments,
translations, lock files and images are not counted
A PR to `main` whose CI runs in the contributor's fork (AIP-120: the `Fork CI gate` job would skip
project CI for it) is decided by one rule instead, because its `Tests (AMD)` run only executes the
gate: its runs are approved whatever its size, cost or dependency changes, unless it changes a file
the fork CI reconciler counts as CI-defining (`.github/`, `dev/breeze/`, `scripts/ci/`,
`scripts/docker/`, `scripts/in_container/`, `Dockerfile*`, `.pre-commit-config.yaml`). Such a PR gets
the `need workflow approval` label and one comment telling the author that the CI change needs a
maintainer; see
[Approving gated runs](08_fork_ci_for_external_contributors.md#approving-gated-runs).
Only runs created in the last 24 hours (`--max-run-age-hours`) are considered, so the workflow never
approves a backlog of older waiting runs at once; those stay with maintainers. A commit that is the
head of several open PRs is never approved automatically, because its runs cannot be told apart by
PR, and only runs started after the PR was opened and after its base branch last changed are
approved (retargeting a PR does not start new runs). A run selects its jobs from the labels the PR
had when it started, while the decision uses the current labels, so for PRs that run project CI a
run started before a label that changes which tests run was added or removed
(`full tests needed`, `all versions`, `default versions only`, `latest versions only`, `canary`,
`upgrade to newer dependencies`, `area:e2e-tests`, `area:kubernetes-tests`) is left for a
maintainer, since it still runs the selection it started with; the next push is decided afresh.
For a PR whose CI runs in the fork the selection does not matter, but the gate falls back to the
labels a run started with when it cannot read the current ones, so a run started before
`use fork ci` or `use project ci` was added or removed is left for a maintainer instead. Other PRs get the
`need workflow approval` label and wait for a maintainer. When processing a PR fails (for example on
a GitHub API error), the workflow tries to add the label to it and the workflow run fails, so the
failure stays visible even if the label could not be added. The label is removed when a later commit
of the PR is approved automatically.
The workflow uses only `main`'s code. It reads the PR's metadata and fetches its merge commit as git
objects to list the changed files; it never checks out or runs the PR's code. The merge commit must
have been built from the head commit whose runs get approved, so a push after the check cannot slip
into an approved run. Approving with the workflow's `GITHUB_TOKEN` starts the runs right away, and
`workflow_run` workflows that follow them still trigger.
The estimate comes from `dev/breeze/src/airflow_breeze/utils/ci_minutes_estimate.py`, which turns the
selective-check outputs into job groups with job-minutes and a wall-clock estimate, weighting
providers by the size of their test suites. The job summary lists every waiting PR with its decision,
the expected job groups and the estimated time. The numbers are medians of recent `Tests (AMD)` PR
runs on public runners; refresh them with `dev/calibrate_ci_minutes_estimates.py`, as
`.github/AGENTS.md` describes. Run `breeze ci approve-workflow-runs --report-only` (or dispatch the
workflow with `report-only`) to see the decisions without approving or labelling anything.
## Fork push run
These runs are triggered by a push to any branch of a contributor's fork in which the
@@ -337,6 +337,49 @@ where `*` is any characters except `/`).
The `check-ci-workflows-in-sync` prek hook allowlist is extended to permit the `gate` job and
the `build-info` dependency to exist only in the AMD workflow.
### Approving gated runs
GitHub holds the `pull_request` runs of a fork pull request in `action_required` until someone with
write access approves them, for the contributors the repository's approval setting covers. Until
then the gate job does not run either. A gated pull request would stay in the maintainers' approval
queue even though its project CI run only executes the gate, and the reconciler could not delete a
gated run that never ran.
`breeze ci approve-workflow-runs`, run every 30 minutes by `auto-approve-workflow-runs.yml` (see
[CI workflows](05_workflows.md#approval-of-pull-request-runs)), handles them. For a pull request to
`main` it calls `fork_ci_gate.decide` with the configuration of its own `main` checkout and the
pull request's author, association, creation time, head repository and current labels, the inputs
the gate job of the waiting run uses. When the reason is gated (`external`, `pilot` or
`label_fork_ci`), the rules for pull requests that run project CI (estimated job-minutes, lines of
source code, dependency files, full tests) do not apply, because none of those jobs run. One rule
applies instead:
- The waiting runs of the head commit are approved when the pull request's merge commit changes no
file that the reconciler counts as CI-defining: `.github/`, `dev/breeze/`, `scripts/ci/`,
`scripts/docker/`, `scripts/in_container/`, `Dockerfile*` and `.pre-commit-config.yaml` (see
"Trusting a green fork run"). The approved `ci-amd.yml` run executes only the gate, and the other
`pull_request` workflows (CodeQL for the changed languages, the newsfragment check, dependency
review) run on GitHub-hosted runners.
- Otherwise the runs wait for a maintainer, the pull request is labelled `need workflow approval`,
and the workflow posts one comment explaining that the change to the listed CI files needs a
maintainer to review it and approve the runs. The comment starts with the marker
`<!-- workflow-approval: ci-change -->` and is posted once per pull request; only comments by
`github-actions[bot]` with the marker count. A change to `.github/` can remove the gate or add a
workflow that does run, so such a run must not start without a review.
A run started before `use fork ci` or `use project ci` was last added or removed is not approved
automatically: the gate falls back to the labels of the run's event when it cannot read the current
ones, and those can still send the run to project CI. The next push is decided afresh.
Both rules use the same list as "Trusting a green fork run", so a gated pull request whose runs are
approved automatically is also one whose green fork run is trusted, and one that changes CI is both
left waiting for approval and labelled `needs project ci` once its fork run is green. The gate
tampering check still applies to every run a maintainer approves by hand.
The approval reads only API metadata and the git objects of the merge commit; it never checks out or
runs the pull request's code. Pull requests to other branches keep the rules for project CI, which
leave them to a maintainer.
## Fork mode CI
### Trigger
@@ -1022,6 +1065,9 @@ as a failed check rather than a crash.
see "Trusting a green fork run".
- Job statuses carry names from the contributor's workflow under their own `Fork CI job / <name>`
prefix, so they cannot post the reconciler's own contexts or `CI passed`; see "Job statuses".
- Workflow runs of a gated pull request are approved without a maintainer only when it changes no
CI-defining file, so a pull request that edits the gate away still waits for a maintainer before
any run starts; see "Approving gated runs".
- The reconciler runs on `main` with `pull-requests: write` and never checks out or executes
pull request content. Its inputs are API data; its comment templates are static.
- Fork runs get the fork's `GITHUB_TOKEN` only. Nothing in project CI trusts a fork run's
+27 -15
View File
@@ -1,4 +1,4 @@
<svg class="rich-terminal" viewBox="0 0 1482 562.4" xmlns="http://www.w3.org/2000/svg">
<svg class="rich-terminal" viewBox="0 0 1482 635.5999999999999" xmlns="http://www.w3.org/2000/svg">
<!-- Generated with Rich https://www.textualize.io -->
<style>
@@ -42,7 +42,7 @@
<defs>
<clipPath id="breeze-ci-clip-terminal">
<rect x="0" y="0" width="1463.0" height="511.4" />
<rect x="0" y="0" width="1463.0" height="584.5999999999999" />
</clipPath>
<clipPath id="breeze-ci-line-0">
<rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -104,9 +104,18 @@
<clipPath id="breeze-ci-line-19">
<rect x="0" y="465.1" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-line-20">
<rect x="0" y="489.5" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-line-21">
<rect x="0" y="513.9" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-line-22">
<rect x="0" y="538.3" width="1464" height="24.65"/>
</clipPath>
</defs>
<rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="560.4" rx="8"/><text class="breeze-ci-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command:&#160;ci</text>
<rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="633.6" rx="8"/><text class="breeze-ci-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command:&#160;ci</text>
<g transform="translate(26,22)">
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
@@ -125,18 +134,21 @@
</text><text class="breeze-ci-r5" x="0" y="166.4" textLength="12.2" clip-path="url(#breeze-ci-line-6)">│</text><text class="breeze-ci-r4" x="24.4" y="166.4" textLength="73.2" clip-path="url(#breeze-ci-line-6)">--help</text><text class="breeze-ci-r6" x="122" y="166.4" textLength="24.4" clip-path="url(#breeze-ci-line-6)">-h</text><text class="breeze-ci-r1" x="170.8" y="166.4" textLength="329.4" clip-path="url(#breeze-ci-line-6)">Show&#160;this&#160;message&#160;and&#160;exit.</text><text class="breeze-ci-r5" x="1451.8" y="166.4" textLength="12.2" clip-path="url(#breeze-ci-line-6)">│</text><text class="breeze-ci-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#breeze-ci-line-6)">
</text><text class="breeze-ci-r5" x="0" y="190.8" textLength="1464" clip-path="url(#breeze-ci-line-7)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-ci-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#breeze-ci-line-7)">
</text><text class="breeze-ci-r5" x="0" y="215.2" textLength="24.4" clip-path="url(#breeze-ci-line-8)">╭─</text><text class="breeze-ci-r5" x="24.4" y="215.2" textLength="158.6" clip-path="url(#breeze-ci-line-8)">&#160;CI&#160;commands&#160;</text><text class="breeze-ci-r5" x="183" y="215.2" textLength="1256.6" clip-path="url(#breeze-ci-line-8)">───────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="breeze-ci-r5" x="1439.6" y="215.2" textLength="24.4" clip-path="url(#breeze-ci-line-8)">─╮</text><text class="breeze-ci-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#breeze-ci-line-8)">
</text><text class="breeze-ci-r5" x="0" y="239.6" textLength="12.2" clip-path="url(#breeze-ci-line-9)">│</text><text class="breeze-ci-r4" x="24.4" y="239.6" textLength="207.4" clip-path="url(#breeze-ci-line-9)">fix-ownership&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r1" x="256.2" y="239.6" textLength="1183.4" clip-path="url(#breeze-ci-line-9)">Fix&#160;ownership&#160;of&#160;source&#160;files&#160;to&#160;be&#160;same&#160;as&#160;host&#160;user.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="239.6" textLength="12.2" clip-path="url(#breeze-ci-line-9)">│</text><text class="breeze-ci-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#breeze-ci-line-9)">
</text><text class="breeze-ci-r5" x="0" y="264" textLength="12.2" clip-path="url(#breeze-ci-line-10)">│</text><text class="breeze-ci-r4" x="24.4" y="264" textLength="207.4" clip-path="url(#breeze-ci-line-10)">free-space&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r1" x="256.2" y="264" textLength="1183.4" clip-path="url(#breeze-ci-line-10)">Free&#160;space&#160;for&#160;jobs&#160;run&#160;in&#160;CI.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="264" textLength="12.2" clip-path="url(#breeze-ci-line-10)">│</text><text class="breeze-ci-r1" x="1464" y="264" textLength="12.2" clip-path="url(#breeze-ci-line-10)">
</text><text class="breeze-ci-r5" x="0" y="288.4" textLength="12.2" clip-path="url(#breeze-ci-line-11)">│</text><text class="breeze-ci-r4" x="24.4" y="288.4" textLength="207.4" clip-path="url(#breeze-ci-line-11)">resource-check&#160;&#160;&#160;</text><text class="breeze-ci-r1" x="256.2" y="288.4" textLength="1183.4" clip-path="url(#breeze-ci-line-11)">Check&#160;if&#160;available&#160;docker&#160;resources&#160;are&#160;enough.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="288.4" textLength="12.2" clip-path="url(#breeze-ci-line-11)">│</text><text class="breeze-ci-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#breeze-ci-line-11)">
</text><text class="breeze-ci-r5" x="0" y="312.8" textLength="12.2" clip-path="url(#breeze-ci-line-12)">│</text><text class="breeze-ci-r4" x="24.4" y="312.8" textLength="207.4" clip-path="url(#breeze-ci-line-12)">selective-check&#160;&#160;</text><text class="breeze-ci-r1" x="256.2" y="312.8" textLength="1183.4" clip-path="url(#breeze-ci-line-12)">Checks&#160;what&#160;kind&#160;of&#160;tests&#160;should&#160;be&#160;run&#160;for&#160;an&#160;incoming&#160;commit.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="312.8" textLength="12.2" clip-path="url(#breeze-ci-line-12)">│</text><text class="breeze-ci-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#breeze-ci-line-12)">
</text><text class="breeze-ci-r5" x="0" y="337.2" textLength="12.2" clip-path="url(#breeze-ci-line-13)">│</text><text class="breeze-ci-r4" x="24.4" y="337.2" textLength="207.4" clip-path="url(#breeze-ci-line-13)">get-workflow-info</text><text class="breeze-ci-r1" x="256.2" y="337.2" textLength="1183.4" clip-path="url(#breeze-ci-line-13)">Retrieve&#160;information&#160;about&#160;current&#160;workflow&#160;in&#160;the&#160;CIand&#160;produce&#160;github&#160;actions&#160;output&#160;extracted&#160;</text><text class="breeze-ci-r5" x="1451.8" y="337.2" textLength="12.2" clip-path="url(#breeze-ci-line-13)">│</text><text class="breeze-ci-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#breeze-ci-line-13)">
</text><text class="breeze-ci-r5" x="0" y="361.6" textLength="12.2" clip-path="url(#breeze-ci-line-14)">│</text><text class="breeze-ci-r1" x="256.2" y="361.6" textLength="1183.4" clip-path="url(#breeze-ci-line-14)">from&#160;it.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="361.6" textLength="12.2" clip-path="url(#breeze-ci-line-14)">│</text><text class="breeze-ci-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#breeze-ci-line-14)">
</text><text class="breeze-ci-r5" x="0" y="386" textLength="12.2" clip-path="url(#breeze-ci-line-15)">│</text><text class="breeze-ci-r4" x="24.4" y="386" textLength="207.4" clip-path="url(#breeze-ci-line-15)">upgrade&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r1" x="256.2" y="386" textLength="1183.4" clip-path="url(#breeze-ci-line-15)">Perform&#160;important&#160;upgrade&#160;steps&#160;of&#160;the&#160;CI&#160;environment.&#160;And&#160;create&#160;a&#160;PR&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="386" textLength="12.2" clip-path="url(#breeze-ci-line-15)">│</text><text class="breeze-ci-r1" x="1464" y="386" textLength="12.2" clip-path="url(#breeze-ci-line-15)">
</text><text class="breeze-ci-r5" x="0" y="410.4" textLength="12.2" clip-path="url(#breeze-ci-line-16)">│</text><text class="breeze-ci-r4" x="24.4" y="410.4" textLength="207.4" clip-path="url(#breeze-ci-line-16)">set-milestone&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r1" x="256.2" y="410.4" textLength="1183.4" clip-path="url(#breeze-ci-line-16)">Set&#160;milestone&#160;on&#160;a&#160;merged&#160;PR&#160;if&#160;it&#160;doesn&#x27;t&#160;have&#160;one.&#160;Used&#160;by&#160;the&#160;milestone-tag-assistant&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="410.4" textLength="12.2" clip-path="url(#breeze-ci-line-16)">│</text><text class="breeze-ci-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#breeze-ci-line-16)">
</text><text class="breeze-ci-r5" x="0" y="434.8" textLength="12.2" clip-path="url(#breeze-ci-line-17)">│</text><text class="breeze-ci-r1" x="256.2" y="434.8" textLength="1183.4" clip-path="url(#breeze-ci-line-17)">workflow.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="434.8" textLength="12.2" clip-path="url(#breeze-ci-line-17)">│</text><text class="breeze-ci-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#breeze-ci-line-17)">
</text><text class="breeze-ci-r5" x="0" y="459.2" textLength="12.2" clip-path="url(#breeze-ci-line-18)">│</text><text class="breeze-ci-r4" x="24.4" y="459.2" textLength="207.4" clip-path="url(#breeze-ci-line-18)">fork-ci-reconcile</text><text class="breeze-ci-r1" x="256.2" y="459.2" textLength="1183.4" clip-path="url(#breeze-ci-line-18)">Reconcile&#160;pull&#160;requests&#160;whose&#160;CI&#160;runs&#160;in&#160;contributor&#160;forks&#160;(AIP-120).&#160;Run&#160;on&#160;a&#160;schedule.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="459.2" textLength="12.2" clip-path="url(#breeze-ci-line-18)">│</text><text class="breeze-ci-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#breeze-ci-line-18)">
</text><text class="breeze-ci-r5" x="0" y="483.6" textLength="12.2" clip-path="url(#breeze-ci-line-19)">│</text><text class="breeze-ci-r4" x="24.4" y="483.6" textLength="207.4" clip-path="url(#breeze-ci-line-19)">audit&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r1" x="256.2" y="483.6" textLength="1183.4" clip-path="url(#breeze-ci-line-19)">Check&#160;that&#160;your&#160;checkout&#160;and&#160;fork&#160;are&#160;set&#160;up&#160;for&#160;fork&#160;CI&#160;(AIP-120),&#160;and&#160;offer&#160;the&#160;fixes.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="483.6" textLength="12.2" clip-path="url(#breeze-ci-line-19)">│</text><text class="breeze-ci-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#breeze-ci-line-19)">
</text><text class="breeze-ci-r5" x="0" y="508" textLength="1464" clip-path="url(#breeze-ci-line-20)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-ci-r1" x="1464" y="508" textLength="12.2" clip-path="url(#breeze-ci-line-20)">
</text><text class="breeze-ci-r5" x="0" y="239.6" textLength="12.2" clip-path="url(#breeze-ci-line-9)">│</text><text class="breeze-ci-r4" x="24.4" y="239.6" textLength="256.2" clip-path="url(#breeze-ci-line-9)">fix-ownership&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r1" x="305" y="239.6" textLength="1134.6" clip-path="url(#breeze-ci-line-9)">Fix&#160;ownership&#160;of&#160;source&#160;files&#160;to&#160;be&#160;same&#160;as&#160;host&#160;user.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="239.6" textLength="12.2" clip-path="url(#breeze-ci-line-9)">│</text><text class="breeze-ci-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#breeze-ci-line-9)">
</text><text class="breeze-ci-r5" x="0" y="264" textLength="12.2" clip-path="url(#breeze-ci-line-10)">│</text><text class="breeze-ci-r4" x="24.4" y="264" textLength="256.2" clip-path="url(#breeze-ci-line-10)">free-space&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r1" x="305" y="264" textLength="1134.6" clip-path="url(#breeze-ci-line-10)">Free&#160;space&#160;for&#160;jobs&#160;run&#160;in&#160;CI.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="264" textLength="12.2" clip-path="url(#breeze-ci-line-10)">│</text><text class="breeze-ci-r1" x="1464" y="264" textLength="12.2" clip-path="url(#breeze-ci-line-10)">
</text><text class="breeze-ci-r5" x="0" y="288.4" textLength="12.2" clip-path="url(#breeze-ci-line-11)">│</text><text class="breeze-ci-r4" x="24.4" y="288.4" textLength="256.2" clip-path="url(#breeze-ci-line-11)">resource-check&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r1" x="305" y="288.4" textLength="1134.6" clip-path="url(#breeze-ci-line-11)">Check&#160;if&#160;available&#160;docker&#160;resources&#160;are&#160;enough.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="288.4" textLength="12.2" clip-path="url(#breeze-ci-line-11)">│</text><text class="breeze-ci-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#breeze-ci-line-11)">
</text><text class="breeze-ci-r5" x="0" y="312.8" textLength="12.2" clip-path="url(#breeze-ci-line-12)">│</text><text class="breeze-ci-r4" x="24.4" y="312.8" textLength="256.2" clip-path="url(#breeze-ci-line-12)">selective-check&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r1" x="305" y="312.8" textLength="1134.6" clip-path="url(#breeze-ci-line-12)">Checks&#160;what&#160;kind&#160;of&#160;tests&#160;should&#160;be&#160;run&#160;for&#160;an&#160;incoming&#160;commit.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="312.8" textLength="12.2" clip-path="url(#breeze-ci-line-12)">│</text><text class="breeze-ci-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#breeze-ci-line-12)">
</text><text class="breeze-ci-r5" x="0" y="337.2" textLength="12.2" clip-path="url(#breeze-ci-line-13)">│</text><text class="breeze-ci-r4" x="24.4" y="337.2" textLength="256.2" clip-path="url(#breeze-ci-line-13)">get-workflow-info&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r1" x="305" y="337.2" textLength="1134.6" clip-path="url(#breeze-ci-line-13)">Retrieve&#160;information&#160;about&#160;current&#160;workflow&#160;in&#160;the&#160;CIand&#160;produce&#160;github&#160;actions&#160;output&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="337.2" textLength="12.2" clip-path="url(#breeze-ci-line-13)">│</text><text class="breeze-ci-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#breeze-ci-line-13)">
</text><text class="breeze-ci-r5" x="0" y="361.6" textLength="12.2" clip-path="url(#breeze-ci-line-14)">│</text><text class="breeze-ci-r1" x="305" y="361.6" textLength="1134.6" clip-path="url(#breeze-ci-line-14)">extracted&#160;from&#160;it.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="361.6" textLength="12.2" clip-path="url(#breeze-ci-line-14)">│</text><text class="breeze-ci-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#breeze-ci-line-14)">
</text><text class="breeze-ci-r5" x="0" y="386" textLength="12.2" clip-path="url(#breeze-ci-line-15)">│</text><text class="breeze-ci-r4" x="24.4" y="386" textLength="256.2" clip-path="url(#breeze-ci-line-15)">upgrade&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r1" x="305" y="386" textLength="1134.6" clip-path="url(#breeze-ci-line-15)">Perform&#160;important&#160;upgrade&#160;steps&#160;of&#160;the&#160;CI&#160;environment.&#160;And&#160;create&#160;a&#160;PR&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="386" textLength="12.2" clip-path="url(#breeze-ci-line-15)">│</text><text class="breeze-ci-r1" x="1464" y="386" textLength="12.2" clip-path="url(#breeze-ci-line-15)">
</text><text class="breeze-ci-r5" x="0" y="410.4" textLength="12.2" clip-path="url(#breeze-ci-line-16)">│</text><text class="breeze-ci-r4" x="24.4" y="410.4" textLength="256.2" clip-path="url(#breeze-ci-line-16)">set-milestone&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r1" x="305" y="410.4" textLength="1134.6" clip-path="url(#breeze-ci-line-16)">Set&#160;milestone&#160;on&#160;a&#160;merged&#160;PR&#160;if&#160;it&#160;doesn&#x27;t&#160;have&#160;one.&#160;Used&#160;by&#160;the&#160;milestone-tag-assistant&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="410.4" textLength="12.2" clip-path="url(#breeze-ci-line-16)">│</text><text class="breeze-ci-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#breeze-ci-line-16)">
</text><text class="breeze-ci-r5" x="0" y="434.8" textLength="12.2" clip-path="url(#breeze-ci-line-17)">│</text><text class="breeze-ci-r1" x="305" y="434.8" textLength="1134.6" clip-path="url(#breeze-ci-line-17)">workflow.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="434.8" textLength="12.2" clip-path="url(#breeze-ci-line-17)">│</text><text class="breeze-ci-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#breeze-ci-line-17)">
</text><text class="breeze-ci-r5" x="0" y="459.2" textLength="12.2" clip-path="url(#breeze-ci-line-18)">│</text><text class="breeze-ci-r4" x="24.4" y="459.2" textLength="256.2" clip-path="url(#breeze-ci-line-18)">fork-ci-reconcile&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r1" x="305" y="459.2" textLength="1134.6" clip-path="url(#breeze-ci-line-18)">Reconcile&#160;pull&#160;requests&#160;whose&#160;CI&#160;runs&#160;in&#160;contributor&#160;forks&#160;(AIP-120).&#160;Run&#160;on&#160;a&#160;schedule.&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="459.2" textLength="12.2" clip-path="url(#breeze-ci-line-18)">│</text><text class="breeze-ci-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#breeze-ci-line-18)">
</text><text class="breeze-ci-r5" x="0" y="483.6" textLength="12.2" clip-path="url(#breeze-ci-line-19)">│</text><text class="breeze-ci-r4" x="24.4" y="483.6" textLength="256.2" clip-path="url(#breeze-ci-line-19)">audit&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r1" x="305" y="483.6" textLength="1134.6" clip-path="url(#breeze-ci-line-19)">Check&#160;that&#160;your&#160;checkout&#160;and&#160;fork&#160;are&#160;set&#160;up&#160;for&#160;fork&#160;CI&#160;(AIP-120),&#160;and&#160;offer&#160;the&#160;fixes.&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="483.6" textLength="12.2" clip-path="url(#breeze-ci-line-19)">│</text><text class="breeze-ci-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#breeze-ci-line-19)">
</text><text class="breeze-ci-r5" x="0" y="508" textLength="12.2" clip-path="url(#breeze-ci-line-20)">│</text><text class="breeze-ci-r4" x="24.4" y="508" textLength="256.2" clip-path="url(#breeze-ci-line-20)">approve-workflow-runs</text><text class="breeze-ci-r1" x="305" y="508" textLength="1134.6" clip-path="url(#breeze-ci-line-20)">Approve&#160;the&#160;waiting&#160;workflow&#160;runs&#160;of&#160;external&#160;contributors&#x27;&#160;PRs&#160;whose&#160;changes&#160;are&#160;safe&#160;to&#160;run</text><text class="breeze-ci-r5" x="1451.8" y="508" textLength="12.2" clip-path="url(#breeze-ci-line-20)">│</text><text class="breeze-ci-r1" x="1464" y="508" textLength="12.2" clip-path="url(#breeze-ci-line-20)">
</text><text class="breeze-ci-r5" x="0" y="532.4" textLength="12.2" clip-path="url(#breeze-ci-line-21)">│</text><text class="breeze-ci-r1" x="305" y="532.4" textLength="1134.6" clip-path="url(#breeze-ci-line-21)">without&#160;a&#160;maintainer&#x27;s&#160;look,&#160;and&#160;label&#160;the&#160;others&#160;&#x27;need&#160;workflow&#160;approval&#x27;.&#160;Used&#160;by&#160;the&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="532.4" textLength="12.2" clip-path="url(#breeze-ci-line-21)">│</text><text class="breeze-ci-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#breeze-ci-line-21)">
</text><text class="breeze-ci-r5" x="0" y="556.8" textLength="12.2" clip-path="url(#breeze-ci-line-22)">│</text><text class="breeze-ci-r1" x="305" y="556.8" textLength="1134.6" clip-path="url(#breeze-ci-line-22)">auto-approve-workflow-runs&#160;workflow.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-r5" x="1451.8" y="556.8" textLength="12.2" clip-path="url(#breeze-ci-line-22)">│</text><text class="breeze-ci-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#breeze-ci-line-22)">
</text><text class="breeze-ci-r5" x="0" y="581.2" textLength="1464" clip-path="url(#breeze-ci-line-23)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-ci-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#breeze-ci-line-23)">
</text>
</g>
</g>

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 22 KiB

+1 -1
View File
@@ -1 +1 @@
ceb5f9dfbcb79c72545427568d9b7dbc
eb626e1c2737eaba69f91123d1077d16
@@ -0,0 +1,142 @@
<svg class="rich-terminal" viewBox="0 0 1482 538.0" xmlns="http://www.w3.org/2000/svg">
<!-- Generated with Rich https://www.textualize.io -->
<style>
@font-face {
font-family: "Fira Code";
src: local("FiraCode-Regular"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff");
font-style: normal;
font-weight: 400;
}
@font-face {
font-family: "Fira Code";
src: local("FiraCode-Bold"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff");
font-style: bold;
font-weight: 700;
}
.breeze-ci-approve-workflow-runs-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
.breeze-ci-approve-workflow-runs-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
.breeze-ci-approve-workflow-runs-r1 { fill: #c5c8c6 }
.breeze-ci-approve-workflow-runs-r2 { fill: #d0b344 }
.breeze-ci-approve-workflow-runs-r3 { fill: #c5c8c6;font-weight: bold }
.breeze-ci-approve-workflow-runs-r4 { fill: #68a0b3;font-weight: bold }
.breeze-ci-approve-workflow-runs-r5 { fill: #868887 }
.breeze-ci-approve-workflow-runs-r6 { fill: #8d7b39 }
.breeze-ci-approve-workflow-runs-r7 { fill: #cc555a }
.breeze-ci-approve-workflow-runs-r8 { fill: #8a4346 }
.breeze-ci-approve-workflow-runs-r9 { fill: #98a84b;font-weight: bold }
</style>
<defs>
<clipPath id="breeze-ci-approve-workflow-runs-clip-terminal">
<rect x="0" y="0" width="1463.0" height="487.0" />
</clipPath>
<clipPath id="breeze-ci-approve-workflow-runs-line-0">
<rect x="0" y="1.5" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-approve-workflow-runs-line-1">
<rect x="0" y="25.9" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-approve-workflow-runs-line-2">
<rect x="0" y="50.3" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-approve-workflow-runs-line-3">
<rect x="0" y="74.7" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-approve-workflow-runs-line-4">
<rect x="0" y="99.1" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-approve-workflow-runs-line-5">
<rect x="0" y="123.5" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-approve-workflow-runs-line-6">
<rect x="0" y="147.9" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-approve-workflow-runs-line-7">
<rect x="0" y="172.3" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-approve-workflow-runs-line-8">
<rect x="0" y="196.7" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-approve-workflow-runs-line-9">
<rect x="0" y="221.1" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-approve-workflow-runs-line-10">
<rect x="0" y="245.5" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-approve-workflow-runs-line-11">
<rect x="0" y="269.9" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-approve-workflow-runs-line-12">
<rect x="0" y="294.3" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-approve-workflow-runs-line-13">
<rect x="0" y="318.7" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-approve-workflow-runs-line-14">
<rect x="0" y="343.1" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-approve-workflow-runs-line-15">
<rect x="0" y="367.5" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-approve-workflow-runs-line-16">
<rect x="0" y="391.9" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-approve-workflow-runs-line-17">
<rect x="0" y="416.3" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-approve-workflow-runs-line-18">
<rect x="0" y="440.7" width="1464" height="24.65"/>
</clipPath>
</defs>
<rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="536" rx="8"/><text class="breeze-ci-approve-workflow-runs-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command:&#160;ci&#160;approve-workflow-runs</text>
<g transform="translate(26,22)">
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
<circle cx="44" cy="0" r="7" fill="#28c840"/>
</g>
<g transform="translate(9, 41)" clip-path="url(#breeze-ci-approve-workflow-runs-clip-terminal)">
<g class="breeze-ci-approve-workflow-runs-matrix">
<text class="breeze-ci-approve-workflow-runs-r1" x="1464" y="20" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-0)">
</text><text class="breeze-ci-approve-workflow-runs-r2" x="12.2" y="44.4" textLength="73.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-1)">Usage:</text><text class="breeze-ci-approve-workflow-runs-r3" x="97.6" y="44.4" textLength="378.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-1)">breeze&#160;ci&#160;approve-workflow-runs</text><text class="breeze-ci-approve-workflow-runs-r1" x="488" y="44.4" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-1)">[</text><text class="breeze-ci-approve-workflow-runs-r4" x="500.2" y="44.4" textLength="85.4" clip-path="url(#breeze-ci-approve-workflow-runs-line-1)">OPTIONS</text><text class="breeze-ci-approve-workflow-runs-r1" x="585.6" y="44.4" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-1)">]</text><text class="breeze-ci-approve-workflow-runs-r1" x="1464" y="44.4" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-1)">
</text><text class="breeze-ci-approve-workflow-runs-r1" x="1464" y="68.8" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-2)">
</text><text class="breeze-ci-approve-workflow-runs-r1" x="12.2" y="93.2" textLength="1427.4" clip-path="url(#breeze-ci-approve-workflow-runs-line-3)">Approve&#160;the&#160;waiting&#160;workflow&#160;runs&#160;of&#160;external&#160;contributors&#x27;&#160;PRs&#160;whose&#160;changes&#160;are&#160;safe&#160;to&#160;run&#160;without&#160;a&#160;maintainer&#x27;s&#160;</text><text class="breeze-ci-approve-workflow-runs-r1" x="1464" y="93.2" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-3)">
</text><text class="breeze-ci-approve-workflow-runs-r1" x="12.2" y="117.6" textLength="1232.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-4)">look,&#160;and&#160;label&#160;the&#160;others&#160;&#x27;need&#160;workflow&#160;approval&#x27;.&#160;Used&#160;by&#160;the&#160;auto-approve-workflow-runs&#160;workflow.</text><text class="breeze-ci-approve-workflow-runs-r1" x="1464" y="117.6" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-4)">
</text><text class="breeze-ci-approve-workflow-runs-r1" x="1464" y="142" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-5)">
</text><text class="breeze-ci-approve-workflow-runs-r5" x="0" y="166.4" textLength="24.4" clip-path="url(#breeze-ci-approve-workflow-runs-line-6)">╭─</text><text class="breeze-ci-approve-workflow-runs-r5" x="24.4" y="166.4" textLength="195.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-6)">&#160;Approval&#160;flags&#160;</text><text class="breeze-ci-approve-workflow-runs-r5" x="219.6" y="166.4" textLength="1220" clip-path="url(#breeze-ci-approve-workflow-runs-line-6)">────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="breeze-ci-approve-workflow-runs-r5" x="1439.6" y="166.4" textLength="24.4" clip-path="url(#breeze-ci-approve-workflow-runs-line-6)">─╮</text><text class="breeze-ci-approve-workflow-runs-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-6)">
</text><text class="breeze-ci-approve-workflow-runs-r5" x="0" y="190.8" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-7)">│</text><text class="breeze-ci-approve-workflow-runs-r4" x="24.4" y="190.8" textLength="231.8" clip-path="url(#breeze-ci-approve-workflow-runs-line-7)">--max-run-age-hours</text><text class="breeze-ci-approve-workflow-runs-r1" x="280.6" y="190.8" textLength="1159" clip-path="url(#breeze-ci-approve-workflow-runs-line-7)">Only&#160;decide&#160;on&#160;workflow&#160;runs&#160;created&#160;within&#160;this&#160;many&#160;hours;&#160;older&#160;waiting&#160;runs&#160;are&#160;left&#160;alone.</text><text class="breeze-ci-approve-workflow-runs-r5" x="1451.8" y="190.8" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-7)">│</text><text class="breeze-ci-approve-workflow-runs-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-7)">
</text><text class="breeze-ci-approve-workflow-runs-r5" x="0" y="215.2" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-8)">│</text><text class="breeze-ci-approve-workflow-runs-r5" x="280.6" y="215.2" textLength="158.6" clip-path="url(#breeze-ci-approve-workflow-runs-line-8)">[default:&#160;24]</text><text class="breeze-ci-approve-workflow-runs-r6" x="451.4" y="215.2" textLength="244" clip-path="url(#breeze-ci-approve-workflow-runs-line-8)">(INTEGER&#160;RANGE&#160;x&gt;=1)</text><text class="breeze-ci-approve-workflow-runs-r5" x="1451.8" y="215.2" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-8)">│</text><text class="breeze-ci-approve-workflow-runs-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-8)">
</text><text class="breeze-ci-approve-workflow-runs-r5" x="0" y="239.6" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-9)">│</text><text class="breeze-ci-approve-workflow-runs-r4" x="24.4" y="239.6" textLength="231.8" clip-path="url(#breeze-ci-approve-workflow-runs-line-9)">--report-only&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-approve-workflow-runs-r1" x="280.6" y="239.6" textLength="1012.6" clip-path="url(#breeze-ci-approve-workflow-runs-line-9)">Only&#160;report&#160;the&#160;decisions&#160;and&#160;estimates,&#160;without&#160;approving&#160;runs&#160;or&#160;changing&#160;labels.</text><text class="breeze-ci-approve-workflow-runs-r5" x="1451.8" y="239.6" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-9)">│</text><text class="breeze-ci-approve-workflow-runs-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-9)">
</text><text class="breeze-ci-approve-workflow-runs-r5" x="0" y="264" textLength="1464" clip-path="url(#breeze-ci-approve-workflow-runs-line-10)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-ci-approve-workflow-runs-r1" x="1464" y="264" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-10)">
</text><text class="breeze-ci-approve-workflow-runs-r5" x="0" y="288.4" textLength="24.4" clip-path="url(#breeze-ci-approve-workflow-runs-line-11)">╭─</text><text class="breeze-ci-approve-workflow-runs-r5" x="24.4" y="288.4" textLength="231.8" clip-path="url(#breeze-ci-approve-workflow-runs-line-11)">&#160;GitHub&#160;parameters&#160;</text><text class="breeze-ci-approve-workflow-runs-r5" x="256.2" y="288.4" textLength="1183.4" clip-path="url(#breeze-ci-approve-workflow-runs-line-11)">─────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="breeze-ci-approve-workflow-runs-r5" x="1439.6" y="288.4" textLength="24.4" clip-path="url(#breeze-ci-approve-workflow-runs-line-11)">─╮</text><text class="breeze-ci-approve-workflow-runs-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-11)">
</text><text class="breeze-ci-approve-workflow-runs-r5" x="0" y="312.8" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-12)">│</text><text class="breeze-ci-approve-workflow-runs-r7" x="24.4" y="312.8" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-12)">*</text><text class="breeze-ci-approve-workflow-runs-r4" x="61" y="312.8" textLength="231.8" clip-path="url(#breeze-ci-approve-workflow-runs-line-12)">--github-token&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-approve-workflow-runs-r1" x="366" y="312.8" textLength="707.6" clip-path="url(#breeze-ci-approve-workflow-runs-line-12)">GitHub&#160;token&#160;able&#160;to&#160;approve&#160;workflow&#160;runs&#160;and&#160;label&#160;PRs.&#160;</text><text class="breeze-ci-approve-workflow-runs-r8" x="1073.6" y="312.8" textLength="122" clip-path="url(#breeze-ci-approve-workflow-runs-line-12)">[required]</text><text class="breeze-ci-approve-workflow-runs-r6" x="1207.8" y="312.8" textLength="73.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-12)">(TEXT)</text><text class="breeze-ci-approve-workflow-runs-r5" x="1451.8" y="312.8" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-12)">│</text><text class="breeze-ci-approve-workflow-runs-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-12)">
</text><text class="breeze-ci-approve-workflow-runs-r5" x="0" y="337.2" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-13)">│</text><text class="breeze-ci-approve-workflow-runs-r4" x="61" y="337.2" textLength="231.8" clip-path="url(#breeze-ci-approve-workflow-runs-line-13)">--github-repository</text><text class="breeze-ci-approve-workflow-runs-r9" x="317.2" y="337.2" textLength="24.4" clip-path="url(#breeze-ci-approve-workflow-runs-line-13)">-g</text><text class="breeze-ci-approve-workflow-runs-r1" x="366" y="337.2" textLength="597.8" clip-path="url(#breeze-ci-approve-workflow-runs-line-13)">GitHub&#160;repository&#160;used&#160;to&#160;pull,&#160;push&#160;run&#160;images.&#160;</text><text class="breeze-ci-approve-workflow-runs-r5" x="963.8" y="337.2" textLength="305" clip-path="url(#breeze-ci-approve-workflow-runs-line-13)">[default:&#160;apache/airflow]</text><text class="breeze-ci-approve-workflow-runs-r6" x="1281" y="337.2" textLength="73.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-13)">(TEXT)</text><text class="breeze-ci-approve-workflow-runs-r5" x="1451.8" y="337.2" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-13)">│</text><text class="breeze-ci-approve-workflow-runs-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-13)">
</text><text class="breeze-ci-approve-workflow-runs-r5" x="0" y="361.6" textLength="1464" clip-path="url(#breeze-ci-approve-workflow-runs-line-14)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-ci-approve-workflow-runs-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-14)">
</text><text class="breeze-ci-approve-workflow-runs-r5" x="0" y="386" textLength="24.4" clip-path="url(#breeze-ci-approve-workflow-runs-line-15)">╭─</text><text class="breeze-ci-approve-workflow-runs-r5" x="24.4" y="386" textLength="195.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-15)">&#160;Common&#160;options&#160;</text><text class="breeze-ci-approve-workflow-runs-r5" x="219.6" y="386" textLength="1220" clip-path="url(#breeze-ci-approve-workflow-runs-line-15)">────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="breeze-ci-approve-workflow-runs-r5" x="1439.6" y="386" textLength="24.4" clip-path="url(#breeze-ci-approve-workflow-runs-line-15)">─╮</text><text class="breeze-ci-approve-workflow-runs-r1" x="1464" y="386" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-15)">
</text><text class="breeze-ci-approve-workflow-runs-r5" x="0" y="410.4" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-16)">│</text><text class="breeze-ci-approve-workflow-runs-r4" x="24.4" y="410.4" textLength="109.8" clip-path="url(#breeze-ci-approve-workflow-runs-line-16)">--verbose</text><text class="breeze-ci-approve-workflow-runs-r9" x="158.6" y="410.4" textLength="24.4" clip-path="url(#breeze-ci-approve-workflow-runs-line-16)">-v</text><text class="breeze-ci-approve-workflow-runs-r1" x="207.4" y="410.4" textLength="585.6" clip-path="url(#breeze-ci-approve-workflow-runs-line-16)">Print&#160;verbose&#160;information&#160;about&#160;performed&#160;steps.</text><text class="breeze-ci-approve-workflow-runs-r5" x="1451.8" y="410.4" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-16)">│</text><text class="breeze-ci-approve-workflow-runs-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-16)">
</text><text class="breeze-ci-approve-workflow-runs-r5" x="0" y="434.8" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-17)">│</text><text class="breeze-ci-approve-workflow-runs-r4" x="24.4" y="434.8" textLength="109.8" clip-path="url(#breeze-ci-approve-workflow-runs-line-17)">--dry-run</text><text class="breeze-ci-approve-workflow-runs-r9" x="158.6" y="434.8" textLength="24.4" clip-path="url(#breeze-ci-approve-workflow-runs-line-17)">-D</text><text class="breeze-ci-approve-workflow-runs-r1" x="207.4" y="434.8" textLength="719.8" clip-path="url(#breeze-ci-approve-workflow-runs-line-17)">If&#160;dry-run&#160;is&#160;set,&#160;commands&#160;are&#160;only&#160;printed,&#160;not&#160;executed.</text><text class="breeze-ci-approve-workflow-runs-r5" x="1451.8" y="434.8" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-17)">│</text><text class="breeze-ci-approve-workflow-runs-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-17)">
</text><text class="breeze-ci-approve-workflow-runs-r5" x="0" y="459.2" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-18)">│</text><text class="breeze-ci-approve-workflow-runs-r4" x="24.4" y="459.2" textLength="109.8" clip-path="url(#breeze-ci-approve-workflow-runs-line-18)">--help&#160;&#160;&#160;</text><text class="breeze-ci-approve-workflow-runs-r9" x="158.6" y="459.2" textLength="24.4" clip-path="url(#breeze-ci-approve-workflow-runs-line-18)">-h</text><text class="breeze-ci-approve-workflow-runs-r1" x="207.4" y="459.2" textLength="329.4" clip-path="url(#breeze-ci-approve-workflow-runs-line-18)">Show&#160;this&#160;message&#160;and&#160;exit.</text><text class="breeze-ci-approve-workflow-runs-r5" x="1451.8" y="459.2" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-18)">│</text><text class="breeze-ci-approve-workflow-runs-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-18)">
</text><text class="breeze-ci-approve-workflow-runs-r5" x="0" y="483.6" textLength="1464" clip-path="url(#breeze-ci-approve-workflow-runs-line-19)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-ci-approve-workflow-runs-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#breeze-ci-approve-workflow-runs-line-19)">
</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 21 KiB

@@ -0,0 +1 @@
485b9682543dc0dd1e1922e32196b2ee
@@ -1,4 +1,4 @@
<svg class="rich-terminal" viewBox="0 0 1482 1294.3999999999999" xmlns="http://www.w3.org/2000/svg">
<svg class="rich-terminal" viewBox="0 0 1482 1318.8" xmlns="http://www.w3.org/2000/svg">
<!-- Generated with Rich https://www.textualize.io -->
<style>
@@ -43,7 +43,7 @@
<defs>
<clipPath id="breeze-setup-check-all-params-in-groups-clip-terminal">
<rect x="0" y="0" width="1463.0" height="1243.3999999999999" />
<rect x="0" y="0" width="1463.0" height="1267.8" />
</clipPath>
<clipPath id="breeze-setup-check-all-params-in-groups-line-0">
<rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -195,9 +195,12 @@
<clipPath id="breeze-setup-check-all-params-in-groups-line-49">
<rect x="0" y="1197.1" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-setup-check-all-params-in-groups-line-50">
<rect x="0" y="1221.5" width="1464" height="24.65"/>
</clipPath>
</defs>
<rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="1292.4" rx="8"/><text class="breeze-setup-check-all-params-in-groups-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command:&#160;setup&#160;check-all-params-in-groups</text>
<rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="1316.8" rx="8"/><text class="breeze-setup-check-all-params-in-groups-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command:&#160;setup&#160;check-all-params-in-groups</text>
<g transform="translate(26,22)">
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
@@ -215,49 +218,50 @@
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="142" textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-5)">╭─</text><text class="breeze-setup-check-all-params-in-groups-r5" x="24.4" y="142" textLength="414.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-5)">&#160;Check&#160;all&#160;params&#160;in&#160;groups&#160;flags&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="439.2" y="142" textLength="1000.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-5)">──────────────────────────────────────────────────────────────────────────────────</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1439.6" y="142" textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-5)">─╮</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="142" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-5)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="166.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-6)">│</text><text class="breeze-setup-check-all-params-in-groups-r4" x="24.4" y="166.4" textLength="109.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-6)">--command</text><text class="breeze-setup-check-all-params-in-groups-r1" x="158.6" y="166.4" textLength="805.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-6)">Command(s)&#160;to&#160;regenerate&#160;images&#160;for&#160;(optional,&#160;might&#160;be&#160;repeated)&#160;</text><text class="breeze-setup-check-all-params-in-groups-r6" x="963.8" y="166.4" textLength="366" clip-path="url(#breeze-setup-check-all-params-in-groups-line-6)">(build-docs&#160;|&#160;ci&#160;|&#160;ci-image&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="166.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-6)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-6)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="190.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-7)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="190.8" textLength="1134.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-7)">ci-image:build&#160;|&#160;ci-image:export-mount-cache&#160;|&#160;ci-image:import-mount-cache&#160;|&#160;ci-image:load&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="190.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-7)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-7)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="215.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-8)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="215.2" textLength="1256.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-8)">ci-image:pull&#160;|&#160;ci-image:save&#160;|&#160;ci-image:verify&#160;|&#160;ci:audit&#160;|&#160;ci:fix-ownership&#160;|&#160;ci:fork-ci-reconcile&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="215.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-8)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-8)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="239.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-9)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="239.6" textLength="1207.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-9)">ci:free-space&#160;|&#160;ci:get-workflow-info&#160;|&#160;ci:resource-check&#160;|&#160;ci:selective-check&#160;|&#160;ci:set-milestone&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="239.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-9)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-9)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="264" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-10)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="264" textLength="1281" clip-path="url(#breeze-setup-check-all-params-in-groups-line-10)">ci:upgrade&#160;|&#160;cleanup&#160;|&#160;doctor&#160;|&#160;down&#160;|&#160;exec&#160;|&#160;generate-migration-file&#160;|&#160;issues&#160;|&#160;issues:unassign&#160;|&#160;k8s&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="264" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-10)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="264" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-10)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="288.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-11)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="288.4" textLength="1073.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-11)">k8s:build-k8s-image&#160;|&#160;k8s:configure-cluster&#160;|&#160;k8s:create-cluster&#160;|&#160;k8s:delete-cluster&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="288.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-11)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-11)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="312.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-12)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="312.8" textLength="1195.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-12)">k8s:deploy-airflow&#160;|&#160;k8s:deploy-cluster&#160;|&#160;k8s:dev&#160;|&#160;k8s:k9s&#160;|&#160;k8s:logs&#160;|&#160;k8s:run-complete-tests&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="312.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-12)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-12)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="337.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-13)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="337.2" textLength="1268.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-13)">k8s:setup-env&#160;|&#160;k8s:setup-lang-sdk-test&#160;|&#160;k8s:shell&#160;|&#160;k8s:smoke-test-overlay&#160;|&#160;k8s:status&#160;|&#160;k8s:tests&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="337.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-13)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-13)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="361.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-14)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="361.6" textLength="1110.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-14)">k8s:upload-k8s-image&#160;|&#160;prod-image&#160;|&#160;prod-image:build&#160;|&#160;prod-image:load&#160;|&#160;prod-image:pull&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="361.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-14)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-14)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="386" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-15)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="386" textLength="1134.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-15)">prod-image:save&#160;|&#160;prod-image:verify&#160;|&#160;registry&#160;|&#160;registry:backfill&#160;|&#160;registry:extract-data&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="386" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-15)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="386" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-15)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="410.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-16)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="410.4" textLength="1098" clip-path="url(#breeze-setup-check-all-params-in-groups-line-16)">registry:publish-versions&#160;|&#160;release-management&#160;|&#160;release-management:add-back-references&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="410.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-16)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-16)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="434.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-17)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="434.8" textLength="1073.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-17)">release-management:check-release-files&#160;|&#160;release-management:classify-provider-changes&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="434.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-17)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-17)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="459.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-18)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="459.2" textLength="1183.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-18)">release-management:clean-old-provider-artifacts&#160;|&#160;release-management:constraints-version-check&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="459.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-18)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-18)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="483.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-19)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="483.6" textLength="1122.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-19)">release-management:create-minor-branch&#160;|&#160;release-management:generate-airflowctl-changelog&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="483.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-19)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-19)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="508" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-20)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="508" textLength="1195.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-20)">release-management:generate-constraints&#160;|&#160;release-management:generate-issue-content-airflow-ctl&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="508" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-20)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="508" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-20)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="532.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-21)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="532.4" textLength="1268.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-21)">release-management:generate-issue-content-core&#160;|&#160;release-management:generate-issue-content-helm-chart&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="532.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-21)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-21)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="556.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-22)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="556.8" textLength="1256.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-22)">release-management:generate-issue-content-providers&#160;|&#160;release-management:generate-providers-metadata&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="556.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-22)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-22)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="581.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-23)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="581.2" textLength="1110.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-23)">release-management:install-provider-distributions&#160;|&#160;release-management:merge-prod-images&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="581.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-23)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-23)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="605.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-24)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="605.6" textLength="1159" clip-path="url(#breeze-setup-check-all-params-in-groups-line-24)">release-management:mirror-base-images&#160;|&#160;release-management:prepare-airflow-ctl-distributions&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="605.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-24)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-24)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="630" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-25)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="630" textLength="1207.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-25)">release-management:prepare-airflow-distributions&#160;|&#160;release-management:prepare-helm-chart-package&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="630" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-25)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="630" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-25)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="654.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-26)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="654.4" textLength="1171.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-26)">release-management:prepare-helm-chart-tarball&#160;|&#160;release-management:prepare-mypy-distributions&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="654.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-26)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-26)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="678.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-27)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="678.8" textLength="1268.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-27)">release-management:prepare-provider-distributions&#160;|&#160;release-management:prepare-provider-documentation&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="678.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-27)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-27)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="703.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-28)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="703.2" textLength="976" clip-path="url(#breeze-setup-check-all-params-in-groups-line-28)">release-management:prepare-python-client&#160;|&#160;release-management:prepare-tarball&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="703.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-28)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-28)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="727.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-29)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="727.6" textLength="1049.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-29)">release-management:prepare-task-sdk-distributions&#160;|&#160;release-management:publish-docs&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="727.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-29)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-29)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="752" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-30)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="752" textLength="1012.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-30)">release-management:publish-docs-to-s3&#160;|&#160;release-management:publish-schemas-to-s3&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="752" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-30)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="752" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-30)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="776.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-31)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="776.4" textLength="988.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-31)">release-management:release-prod-images&#160;|&#160;release-management:start-beta-process&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="776.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-31)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="776.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-31)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="800.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-32)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="800.8" textLength="1281" clip-path="url(#breeze-setup-check-all-params-in-groups-line-32)">release-management:start-rc-process&#160;|&#160;release-management:start-release&#160;|&#160;release-management:tag-providers</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="800.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-32)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="800.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-32)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="825.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-33)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="825.2" textLength="1134.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-33)">|&#160;release-management:update-constraints&#160;|&#160;release-management:update-providers-next-version&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="825.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-33)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="825.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-33)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="849.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-34)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="849.6" textLength="1244.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-34)">release-management:verify-provider-distributions&#160;|&#160;release-management:verify-rc-by-pmc&#160;|&#160;run&#160;|&#160;sbom&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="849.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-34)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="849.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-34)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="874" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-35)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="874" textLength="1281" clip-path="url(#breeze-setup-check-all-params-in-groups-line-35)">sbom:build-all-airflow-images&#160;|&#160;sbom:export-dependency-information&#160;|&#160;sbom:generate-providers-requirements</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="874" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-35)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="874" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-35)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="898.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-36)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="898.4" textLength="1183.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-36)">|&#160;sbom:update-sbom-information&#160;|&#160;setup&#160;|&#160;setup:autocomplete&#160;|&#160;setup:check-all-params-in-groups&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="898.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-36)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="898.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-36)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="922.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-37)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="922.8" textLength="1256.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-37)">setup:config&#160;|&#160;setup:regenerate-command-images&#160;|&#160;setup:self-upgrade&#160;|&#160;setup:synchronize-local-mounts&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="922.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-37)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="922.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-37)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="947.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-38)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="947.2" textLength="1098" clip-path="url(#breeze-setup-check-all-params-in-groups-line-38)">setup:version&#160;|&#160;shell&#160;|&#160;start-airflow&#160;|&#160;testing&#160;|&#160;testing:airflow-ctl-integration-tests&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="947.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-38)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="947.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-38)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="971.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-39)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="971.6" textLength="1085.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-39)">testing:airflow-ctl-tests&#160;|&#160;testing:airflow-e2e-tests&#160;|&#160;testing:core-integration-tests&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="971.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-39)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="971.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-39)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="996" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-40)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="996" textLength="890.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-40)">testing:core-tests&#160;|&#160;testing:docker-compose-tests&#160;|&#160;testing:helm-tests&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="996" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-40)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="996" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-40)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1020.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-41)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="1020.4" textLength="1195.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-41)">testing:providers-integration-tests&#160;|&#160;testing:providers-tests&#160;|&#160;testing:python-api-client-tests&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1020.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-41)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1020.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-41)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1044.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-42)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="1044.8" textLength="1281" clip-path="url(#breeze-setup-check-all-params-in-groups-line-42)">testing:system-tests&#160;|&#160;testing:task-sdk-integration-tests&#160;|&#160;testing:task-sdk-tests&#160;|&#160;testing:ui-e2e-tests</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1044.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-42)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1044.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-42)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1069.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-43)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="1069.2" textLength="1061.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-43)">|&#160;ui&#160;|&#160;ui:check-translation-completeness&#160;|&#160;ui:compile-assets&#160;|&#160;verify&#160;|&#160;workflow-run&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1069.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-43)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1069.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-43)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1093.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-44)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="1093.6" textLength="1183.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-44)">workflow-run:publish-docs&#160;|&#160;workflow-run:release-constraints&#160;|&#160;workflow-run:sync-staging-to-main)</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1093.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-44)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1093.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-44)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1118" textLength="1464" clip-path="url(#breeze-setup-check-all-params-in-groups-line-45)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1118" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-45)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1142.4" textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-46)">╭─</text><text class="breeze-setup-check-all-params-in-groups-r5" x="24.4" y="1142.4" textLength="195.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-46)">&#160;Common&#160;options&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="219.6" y="1142.4" textLength="1220" clip-path="url(#breeze-setup-check-all-params-in-groups-line-46)">────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1439.6" y="1142.4" textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-46)">─╮</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1142.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-46)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1166.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-47)">│</text><text class="breeze-setup-check-all-params-in-groups-r4" x="24.4" y="1166.8" textLength="109.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-47)">--verbose</text><text class="breeze-setup-check-all-params-in-groups-r7" x="158.6" y="1166.8" textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-47)">-v</text><text class="breeze-setup-check-all-params-in-groups-r1" x="207.4" y="1166.8" textLength="585.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-47)">Print&#160;verbose&#160;information&#160;about&#160;performed&#160;steps.</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1166.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-47)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1166.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-47)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1191.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">│</text><text class="breeze-setup-check-all-params-in-groups-r4" x="24.4" y="1191.2" textLength="109.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">--dry-run</text><text class="breeze-setup-check-all-params-in-groups-r7" x="158.6" y="1191.2" textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">-D</text><text class="breeze-setup-check-all-params-in-groups-r1" x="207.4" y="1191.2" textLength="719.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">If&#160;dry-run&#160;is&#160;set,&#160;commands&#160;are&#160;only&#160;printed,&#160;not&#160;executed.</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1191.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1191.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1215.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">│</text><text class="breeze-setup-check-all-params-in-groups-r4" x="24.4" y="1215.6" textLength="109.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">--help&#160;&#160;&#160;</text><text class="breeze-setup-check-all-params-in-groups-r7" x="158.6" y="1215.6" textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">-h</text><text class="breeze-setup-check-all-params-in-groups-r1" x="207.4" y="1215.6" textLength="329.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">Show&#160;this&#160;message&#160;and&#160;exit.</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1215.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1215.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1240" textLength="1464" clip-path="url(#breeze-setup-check-all-params-in-groups-line-50)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1240" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-50)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="215.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-8)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="215.2" textLength="1281" clip-path="url(#breeze-setup-check-all-params-in-groups-line-8)">ci-image:pull&#160;|&#160;ci-image:save&#160;|&#160;ci-image:verify&#160;|&#160;ci:approve-workflow-runs&#160;|&#160;ci:audit&#160;|&#160;ci:fix-ownership&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="215.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-8)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-8)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="239.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-9)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="239.6" textLength="1281" clip-path="url(#breeze-setup-check-all-params-in-groups-line-9)">|&#160;ci:fork-ci-reconcile&#160;|&#160;ci:free-space&#160;|&#160;ci:get-workflow-info&#160;|&#160;ci:resource-check&#160;|&#160;ci:selective-check&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="239.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-9)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-9)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="264" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-10)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="264" textLength="1220" clip-path="url(#breeze-setup-check-all-params-in-groups-line-10)">ci:set-milestone&#160;|&#160;ci:upgrade&#160;|&#160;cleanup&#160;|&#160;doctor&#160;|&#160;down&#160;|&#160;exec&#160;|&#160;generate-migration-file&#160;|&#160;issues&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="264" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-10)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="264" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-10)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="288.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-11)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="288.4" textLength="1110.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-11)">issues:unassign&#160;|&#160;k8s&#160;|&#160;k8s:build-k8s-image&#160;|&#160;k8s:configure-cluster&#160;|&#160;k8s:create-cluster&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="288.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-11)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-11)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="312.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-12)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="312.8" textLength="1146.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-12)">k8s:delete-cluster&#160;|&#160;k8s:deploy-airflow&#160;|&#160;k8s:deploy-cluster&#160;|&#160;k8s:dev&#160;|&#160;k8s:k9s&#160;|&#160;k8s:logs&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="312.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-12)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-12)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="337.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-13)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="337.2" textLength="1268.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-13)">k8s:run-complete-tests&#160;|&#160;k8s:setup-env&#160;|&#160;k8s:setup-lang-sdk-test&#160;|&#160;k8s:shell&#160;|&#160;k8s:smoke-test-overlay&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="337.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-13)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-13)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="361.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-14)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="361.6" textLength="1195.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-14)">k8s:status&#160;|&#160;k8s:tests&#160;|&#160;k8s:upload-k8s-image&#160;|&#160;prod-image&#160;|&#160;prod-image:build&#160;|&#160;prod-image:load&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="361.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-14)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-14)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="386" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-15)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="386" textLength="1061.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-15)">prod-image:pull&#160;|&#160;prod-image:save&#160;|&#160;prod-image:verify&#160;|&#160;registry&#160;|&#160;registry:backfill&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="386" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-15)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="386" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-15)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="410.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-16)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="410.4" textLength="890.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-16)">registry:extract-data&#160;|&#160;registry:publish-versions&#160;|&#160;release-management&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="410.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-16)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-16)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="434.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-17)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="434.8" textLength="1000.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-17)">release-management:add-back-references&#160;|&#160;release-management:check-release-files&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="434.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-17)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-17)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="459.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-18)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="459.2" textLength="1183.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-18)">release-management:classify-provider-changes&#160;|&#160;release-management:clean-old-provider-artifacts&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="459.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-18)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-18)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="483.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-19)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="483.6" textLength="1073.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-19)">release-management:constraints-version-check&#160;|&#160;release-management:create-minor-branch&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="483.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-19)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-19)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="508" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-20)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="508" textLength="1134.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-20)">release-management:generate-airflowctl-changelog&#160;|&#160;release-management:generate-constraints&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="508" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-20)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="508" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-20)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="532.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-21)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="532.4" textLength="1281" clip-path="url(#breeze-setup-check-all-params-in-groups-line-21)">release-management:generate-issue-content-airflow-ctl&#160;|&#160;release-management:generate-issue-content-core&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="532.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-21)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-21)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="556.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-22)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="556.8" textLength="671" clip-path="url(#breeze-setup-check-all-params-in-groups-line-22)">release-management:generate-issue-content-helm-chart&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="556.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-22)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-22)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="581.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-23)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="581.2" textLength="1256.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-23)">release-management:generate-issue-content-providers&#160;|&#160;release-management:generate-providers-metadata&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="581.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-23)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-23)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="605.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-24)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="605.6" textLength="1110.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-24)">release-management:install-provider-distributions&#160;|&#160;release-management:merge-prod-images&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="605.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-24)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-24)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="630" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-25)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="630" textLength="1159" clip-path="url(#breeze-setup-check-all-params-in-groups-line-25)">release-management:mirror-base-images&#160;|&#160;release-management:prepare-airflow-ctl-distributions&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="630" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-25)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="630" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-25)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="654.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-26)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="654.4" textLength="1207.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-26)">release-management:prepare-airflow-distributions&#160;|&#160;release-management:prepare-helm-chart-package&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="654.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-26)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-26)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="678.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-27)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="678.8" textLength="1171.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-27)">release-management:prepare-helm-chart-tarball&#160;|&#160;release-management:prepare-mypy-distributions&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="678.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-27)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-27)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="703.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-28)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="703.2" textLength="1268.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-28)">release-management:prepare-provider-distributions&#160;|&#160;release-management:prepare-provider-documentation&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="703.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-28)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-28)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="727.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-29)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="727.6" textLength="976" clip-path="url(#breeze-setup-check-all-params-in-groups-line-29)">release-management:prepare-python-client&#160;|&#160;release-management:prepare-tarball&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="727.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-29)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-29)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="752" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-30)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="752" textLength="1049.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-30)">release-management:prepare-task-sdk-distributions&#160;|&#160;release-management:publish-docs&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="752" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-30)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="752" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-30)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="776.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-31)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="776.4" textLength="1012.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-31)">release-management:publish-docs-to-s3&#160;|&#160;release-management:publish-schemas-to-s3&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="776.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-31)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="776.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-31)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="800.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-32)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="800.8" textLength="988.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-32)">release-management:release-prod-images&#160;|&#160;release-management:start-beta-process&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="800.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-32)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="800.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-32)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="825.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-33)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="825.2" textLength="1281" clip-path="url(#breeze-setup-check-all-params-in-groups-line-33)">release-management:start-rc-process&#160;|&#160;release-management:start-release&#160;|&#160;release-management:tag-providers</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="825.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-33)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="825.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-33)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="849.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-34)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="849.6" textLength="1134.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-34)">|&#160;release-management:update-constraints&#160;|&#160;release-management:update-providers-next-version&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="849.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-34)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="849.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-34)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="874" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-35)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="874" textLength="1244.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-35)">release-management:verify-provider-distributions&#160;|&#160;release-management:verify-rc-by-pmc&#160;|&#160;run&#160;|&#160;sbom&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="874" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-35)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="874" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-35)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="898.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-36)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="898.4" textLength="1281" clip-path="url(#breeze-setup-check-all-params-in-groups-line-36)">sbom:build-all-airflow-images&#160;|&#160;sbom:export-dependency-information&#160;|&#160;sbom:generate-providers-requirements</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="898.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-36)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="898.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-36)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="922.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-37)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="922.8" textLength="1183.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-37)">|&#160;sbom:update-sbom-information&#160;|&#160;setup&#160;|&#160;setup:autocomplete&#160;|&#160;setup:check-all-params-in-groups&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="922.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-37)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="922.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-37)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="947.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-38)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="947.2" textLength="1256.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-38)">setup:config&#160;|&#160;setup:regenerate-command-images&#160;|&#160;setup:self-upgrade&#160;|&#160;setup:synchronize-local-mounts&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="947.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-38)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="947.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-38)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="971.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-39)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="971.6" textLength="1098" clip-path="url(#breeze-setup-check-all-params-in-groups-line-39)">setup:version&#160;|&#160;shell&#160;|&#160;start-airflow&#160;|&#160;testing&#160;|&#160;testing:airflow-ctl-integration-tests&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="971.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-39)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="971.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-39)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="996" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-40)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="996" textLength="1085.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-40)">testing:airflow-ctl-tests&#160;|&#160;testing:airflow-e2e-tests&#160;|&#160;testing:core-integration-tests&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="996" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-40)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="996" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-40)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1020.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-41)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="1020.4" textLength="890.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-41)">testing:core-tests&#160;|&#160;testing:docker-compose-tests&#160;|&#160;testing:helm-tests&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1020.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-41)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1020.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-41)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1044.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-42)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="1044.8" textLength="1195.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-42)">testing:providers-integration-tests&#160;|&#160;testing:providers-tests&#160;|&#160;testing:python-api-client-tests&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1044.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-42)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1044.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-42)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1069.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-43)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="1069.2" textLength="1281" clip-path="url(#breeze-setup-check-all-params-in-groups-line-43)">testing:system-tests&#160;|&#160;testing:task-sdk-integration-tests&#160;|&#160;testing:task-sdk-tests&#160;|&#160;testing:ui-e2e-tests</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1069.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-43)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1069.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-43)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1093.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-44)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="1093.6" textLength="1061.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-44)">|&#160;ui&#160;|&#160;ui:check-translation-completeness&#160;|&#160;ui:compile-assets&#160;|&#160;verify&#160;|&#160;workflow-run&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1093.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-44)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1093.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-44)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1118" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-45)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="1118" textLength="1183.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-45)">workflow-run:publish-docs&#160;|&#160;workflow-run:release-constraints&#160;|&#160;workflow-run:sync-staging-to-main)</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1118" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-45)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1118" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-45)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1142.4" textLength="1464" clip-path="url(#breeze-setup-check-all-params-in-groups-line-46)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1142.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-46)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1166.8" textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-47)">╭─</text><text class="breeze-setup-check-all-params-in-groups-r5" x="24.4" y="1166.8" textLength="195.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-47)">&#160;Common&#160;options&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="219.6" y="1166.8" textLength="1220" clip-path="url(#breeze-setup-check-all-params-in-groups-line-47)">────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1439.6" y="1166.8" textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-47)">─╮</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1166.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-47)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1191.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">│</text><text class="breeze-setup-check-all-params-in-groups-r4" x="24.4" y="1191.2" textLength="109.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">--verbose</text><text class="breeze-setup-check-all-params-in-groups-r7" x="158.6" y="1191.2" textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">-v</text><text class="breeze-setup-check-all-params-in-groups-r1" x="207.4" y="1191.2" textLength="585.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">Print&#160;verbose&#160;information&#160;about&#160;performed&#160;steps.</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1191.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1191.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1215.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">│</text><text class="breeze-setup-check-all-params-in-groups-r4" x="24.4" y="1215.6" textLength="109.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">--dry-run</text><text class="breeze-setup-check-all-params-in-groups-r7" x="158.6" y="1215.6" textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">-D</text><text class="breeze-setup-check-all-params-in-groups-r1" x="207.4" y="1215.6" textLength="719.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">If&#160;dry-run&#160;is&#160;set,&#160;commands&#160;are&#160;only&#160;printed,&#160;not&#160;executed.</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1215.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1215.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1240" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-50)">│</text><text class="breeze-setup-check-all-params-in-groups-r4" x="24.4" y="1240" textLength="109.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-50)">--help&#160;&#160;&#160;</text><text class="breeze-setup-check-all-params-in-groups-r7" x="158.6" y="1240" textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-50)">-h</text><text class="breeze-setup-check-all-params-in-groups-r1" x="207.4" y="1240" textLength="329.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-50)">Show&#160;this&#160;message&#160;and&#160;exit.</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1240" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-50)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1240" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-50)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1264.4" textLength="1464" clip-path="url(#breeze-setup-check-all-params-in-groups-line-51)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1264.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-51)">
</text>
</g>
</g>

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 50 KiB

@@ -1 +1 @@
525c07613a5bcce29aeb394a631aee01
f171eb2c2e78f67a3ccd1bf977faf841
@@ -1,4 +1,4 @@
<svg class="rich-terminal" viewBox="0 0 1482 1416.3999999999999" xmlns="http://www.w3.org/2000/svg">
<svg class="rich-terminal" viewBox="0 0 1482 1440.8" xmlns="http://www.w3.org/2000/svg">
<!-- Generated with Rich https://www.textualize.io -->
<style>
@@ -43,7 +43,7 @@
<defs>
<clipPath id="breeze-setup-regenerate-command-images-clip-terminal">
<rect x="0" y="0" width="1463.0" height="1365.3999999999999" />
<rect x="0" y="0" width="1463.0" height="1389.8" />
</clipPath>
<clipPath id="breeze-setup-regenerate-command-images-line-0">
<rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -210,9 +210,12 @@
<clipPath id="breeze-setup-regenerate-command-images-line-54">
<rect x="0" y="1319.1" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-setup-regenerate-command-images-line-55">
<rect x="0" y="1343.5" width="1464" height="24.65"/>
</clipPath>
</defs>
<rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="1414.4" rx="8"/><text class="breeze-setup-regenerate-command-images-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command:&#160;setup&#160;regenerate-command-images</text>
<rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="1438.8" rx="8"/><text class="breeze-setup-regenerate-command-images-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command:&#160;setup&#160;regenerate-command-images</text>
<g transform="translate(26,22)">
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
@@ -231,53 +234,54 @@
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="166.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-6)">│</text><text class="breeze-setup-regenerate-command-images-r4" x="24.4" y="166.4" textLength="146.4" clip-path="url(#breeze-setup-regenerate-command-images-line-6)">--force&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-setup-regenerate-command-images-r1" x="195.2" y="166.4" textLength="402.6" clip-path="url(#breeze-setup-regenerate-command-images-line-6)">Forces&#160;regeneration&#160;of&#160;all&#160;images</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="166.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-6)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-6)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="190.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-7)">│</text><text class="breeze-setup-regenerate-command-images-r4" x="24.4" y="190.8" textLength="146.4" clip-path="url(#breeze-setup-regenerate-command-images-line-7)">--command&#160;&#160;&#160;</text><text class="breeze-setup-regenerate-command-images-r1" x="195.2" y="190.8" textLength="805.2" clip-path="url(#breeze-setup-regenerate-command-images-line-7)">Command(s)&#160;to&#160;regenerate&#160;images&#160;for&#160;(optional,&#160;might&#160;be&#160;repeated)&#160;</text><text class="breeze-setup-regenerate-command-images-r6" x="1000.4" y="190.8" textLength="366" clip-path="url(#breeze-setup-regenerate-command-images-line-7)">(build-docs&#160;|&#160;ci&#160;|&#160;ci-image&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="190.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-7)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-7)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="215.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-8)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="215.2" textLength="1134.6" clip-path="url(#breeze-setup-regenerate-command-images-line-8)">ci-image:build&#160;|&#160;ci-image:export-mount-cache&#160;|&#160;ci-image:import-mount-cache&#160;|&#160;ci-image:load&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="215.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-8)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-8)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="239.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-9)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="239.6" textLength="1244.4" clip-path="url(#breeze-setup-regenerate-command-images-line-9)">ci-image:pull&#160;|&#160;ci-image:save&#160;|&#160;ci-image:verify&#160;|&#160;ci:audit&#160;|&#160;ci:fix-ownership&#160;|&#160;ci:fork-ci-reconcile&#160;|</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="239.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-9)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-9)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="264" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-10)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="264" textLength="1207.8" clip-path="url(#breeze-setup-regenerate-command-images-line-10)">ci:free-space&#160;|&#160;ci:get-workflow-info&#160;|&#160;ci:resource-check&#160;|&#160;ci:selective-check&#160;|&#160;ci:set-milestone&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="264" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-10)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="264" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-10)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="288.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-11)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="288.4" textLength="1244.4" clip-path="url(#breeze-setup-regenerate-command-images-line-11)">ci:upgrade&#160;|&#160;cleanup&#160;|&#160;doctor&#160;|&#160;down&#160;|&#160;exec&#160;|&#160;generate-migration-file&#160;|&#160;issues&#160;|&#160;issues:unassign&#160;|&#160;k8s</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="288.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-11)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-11)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="312.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-12)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="312.8" textLength="1098" clip-path="url(#breeze-setup-regenerate-command-images-line-12)">|&#160;k8s:build-k8s-image&#160;|&#160;k8s:configure-cluster&#160;|&#160;k8s:create-cluster&#160;|&#160;k8s:delete-cluster&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="312.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-12)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-12)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="337.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-13)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="337.2" textLength="1195.6" clip-path="url(#breeze-setup-regenerate-command-images-line-13)">k8s:deploy-airflow&#160;|&#160;k8s:deploy-cluster&#160;|&#160;k8s:dev&#160;|&#160;k8s:k9s&#160;|&#160;k8s:logs&#160;|&#160;k8s:run-complete-tests&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="337.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-13)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-13)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="361.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-14)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="361.6" textLength="1244.4" clip-path="url(#breeze-setup-regenerate-command-images-line-14)">k8s:setup-env&#160;|&#160;k8s:setup-lang-sdk-test&#160;|&#160;k8s:shell&#160;|&#160;k8s:smoke-test-overlay&#160;|&#160;k8s:status&#160;|&#160;k8s:tests&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="361.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-14)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-14)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="386" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-15)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="386" textLength="1134.6" clip-path="url(#breeze-setup-regenerate-command-images-line-15)">|&#160;k8s:upload-k8s-image&#160;|&#160;prod-image&#160;|&#160;prod-image:build&#160;|&#160;prod-image:load&#160;|&#160;prod-image:pull&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="386" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-15)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="386" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-15)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="410.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-16)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="410.4" textLength="1134.6" clip-path="url(#breeze-setup-regenerate-command-images-line-16)">prod-image:save&#160;|&#160;prod-image:verify&#160;|&#160;registry&#160;|&#160;registry:backfill&#160;|&#160;registry:extract-data&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="410.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-16)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-16)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="434.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-17)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="434.8" textLength="1098" clip-path="url(#breeze-setup-regenerate-command-images-line-17)">registry:publish-versions&#160;|&#160;release-management&#160;|&#160;release-management:add-back-references&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="434.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-17)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-17)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="459.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-18)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="459.2" textLength="1073.6" clip-path="url(#breeze-setup-regenerate-command-images-line-18)">release-management:check-release-files&#160;|&#160;release-management:classify-provider-changes&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="459.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-18)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-18)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="483.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-19)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="483.6" textLength="1183.4" clip-path="url(#breeze-setup-regenerate-command-images-line-19)">release-management:clean-old-provider-artifacts&#160;|&#160;release-management:constraints-version-check&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="483.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-19)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-19)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="508" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-20)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="508" textLength="1122.4" clip-path="url(#breeze-setup-regenerate-command-images-line-20)">release-management:create-minor-branch&#160;|&#160;release-management:generate-airflowctl-changelog&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="508" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-20)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="508" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-20)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="532.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-21)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="532.4" textLength="1195.6" clip-path="url(#breeze-setup-regenerate-command-images-line-21)">release-management:generate-constraints&#160;|&#160;release-management:generate-issue-content-airflow-ctl&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="532.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-21)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-21)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="556.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-22)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="556.8" textLength="1244.4" clip-path="url(#breeze-setup-regenerate-command-images-line-22)">release-management:generate-issue-content-core&#160;|&#160;release-management:generate-issue-content-helm-chart&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="556.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-22)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-22)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="581.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-23)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="581.2" textLength="1244.4" clip-path="url(#breeze-setup-regenerate-command-images-line-23)">|&#160;release-management:generate-issue-content-providers&#160;|&#160;release-management:generate-providers-metadata</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="581.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-23)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-23)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="605.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-24)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="605.6" textLength="1134.6" clip-path="url(#breeze-setup-regenerate-command-images-line-24)">|&#160;release-management:install-provider-distributions&#160;|&#160;release-management:merge-prod-images&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="605.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-24)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-24)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="630" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-25)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="630" textLength="1159" clip-path="url(#breeze-setup-regenerate-command-images-line-25)">release-management:mirror-base-images&#160;|&#160;release-management:prepare-airflow-ctl-distributions&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="630" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-25)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="630" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-25)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="654.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-26)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="654.4" textLength="1207.8" clip-path="url(#breeze-setup-regenerate-command-images-line-26)">release-management:prepare-airflow-distributions&#160;|&#160;release-management:prepare-helm-chart-package&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="654.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-26)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-26)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="678.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-27)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="678.8" textLength="1171.2" clip-path="url(#breeze-setup-regenerate-command-images-line-27)">release-management:prepare-helm-chart-tarball&#160;|&#160;release-management:prepare-mypy-distributions&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="678.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-27)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-27)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="703.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-28)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="703.2" textLength="1244.4" clip-path="url(#breeze-setup-regenerate-command-images-line-28)">release-management:prepare-provider-distributions&#160;|&#160;release-management:prepare-provider-documentation&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="703.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-28)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-28)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="727.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-29)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="727.6" textLength="1000.4" clip-path="url(#breeze-setup-regenerate-command-images-line-29)">|&#160;release-management:prepare-python-client&#160;|&#160;release-management:prepare-tarball&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="727.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-29)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-29)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="752" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-30)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="752" textLength="1049.2" clip-path="url(#breeze-setup-regenerate-command-images-line-30)">release-management:prepare-task-sdk-distributions&#160;|&#160;release-management:publish-docs&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="752" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-30)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="752" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-30)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="776.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-31)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="776.4" textLength="1012.6" clip-path="url(#breeze-setup-regenerate-command-images-line-31)">release-management:publish-docs-to-s3&#160;|&#160;release-management:publish-schemas-to-s3&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="776.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-31)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="776.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-31)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="800.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-32)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="800.8" textLength="988.2" clip-path="url(#breeze-setup-regenerate-command-images-line-32)">release-management:release-prod-images&#160;|&#160;release-management:start-beta-process&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="800.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-32)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="800.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-32)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="825.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-33)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="825.2" textLength="890.6" clip-path="url(#breeze-setup-regenerate-command-images-line-33)">release-management:start-rc-process&#160;|&#160;release-management:start-release&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="825.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-33)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="825.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-33)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="849.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-34)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="849.6" textLength="915" clip-path="url(#breeze-setup-regenerate-command-images-line-34)">release-management:tag-providers&#160;|&#160;release-management:update-constraints&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="849.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-34)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="849.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-34)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="874" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-35)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="874" textLength="1244.4" clip-path="url(#breeze-setup-regenerate-command-images-line-35)">release-management:update-providers-next-version&#160;|&#160;release-management:verify-provider-distributions&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="874" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-35)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="874" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-35)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="898.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-36)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="898.4" textLength="1012.6" clip-path="url(#breeze-setup-regenerate-command-images-line-36)">release-management:verify-rc-by-pmc&#160;|&#160;run&#160;|&#160;sbom&#160;|&#160;sbom:build-all-airflow-images&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="898.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-36)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="898.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-36)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="922.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-37)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="922.8" textLength="927.2" clip-path="url(#breeze-setup-regenerate-command-images-line-37)">sbom:export-dependency-information&#160;|&#160;sbom:generate-providers-requirements&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="922.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-37)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="922.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-37)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="947.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-38)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="947.2" textLength="1159" clip-path="url(#breeze-setup-regenerate-command-images-line-38)">sbom:update-sbom-information&#160;|&#160;setup&#160;|&#160;setup:autocomplete&#160;|&#160;setup:check-all-params-in-groups&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="947.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-38)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="947.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-38)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="971.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-39)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="971.6" textLength="1244.4" clip-path="url(#breeze-setup-regenerate-command-images-line-39)">setup:config&#160;|&#160;setup:regenerate-command-images&#160;|&#160;setup:self-upgrade&#160;|&#160;setup:synchronize-local-mounts&#160;|</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="971.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-39)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="971.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-39)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="996" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-40)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="996" textLength="1098" clip-path="url(#breeze-setup-regenerate-command-images-line-40)">setup:version&#160;|&#160;shell&#160;|&#160;start-airflow&#160;|&#160;testing&#160;|&#160;testing:airflow-ctl-integration-tests&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="996" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-40)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="996" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-40)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1020.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-41)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="1020.4" textLength="1085.8" clip-path="url(#breeze-setup-regenerate-command-images-line-41)">testing:airflow-ctl-tests&#160;|&#160;testing:airflow-e2e-tests&#160;|&#160;testing:core-integration-tests&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1020.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-41)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1020.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-41)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1044.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-42)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="1044.8" textLength="890.6" clip-path="url(#breeze-setup-regenerate-command-images-line-42)">testing:core-tests&#160;|&#160;testing:docker-compose-tests&#160;|&#160;testing:helm-tests&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1044.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-42)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1044.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-42)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1069.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-43)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="1069.2" textLength="1195.6" clip-path="url(#breeze-setup-regenerate-command-images-line-43)">testing:providers-integration-tests&#160;|&#160;testing:providers-tests&#160;|&#160;testing:python-api-client-tests&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1069.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-43)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1069.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-43)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1093.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-44)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="1093.6" textLength="1037" clip-path="url(#breeze-setup-regenerate-command-images-line-44)">testing:system-tests&#160;|&#160;testing:task-sdk-integration-tests&#160;|&#160;testing:task-sdk-tests&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1093.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-44)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1093.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-44)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1118" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-45)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="1118" textLength="1134.6" clip-path="url(#breeze-setup-regenerate-command-images-line-45)">testing:ui-e2e-tests&#160;|&#160;ui&#160;|&#160;ui:check-translation-completeness&#160;|&#160;ui:compile-assets&#160;|&#160;verify&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1118" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-45)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1118" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-45)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1142.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-46)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="1142.4" textLength="951.6" clip-path="url(#breeze-setup-regenerate-command-images-line-46)">workflow-run&#160;|&#160;workflow-run:publish-docs&#160;|&#160;workflow-run:release-constraints&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1142.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-46)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1142.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-46)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1166.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-47)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="1166.8" textLength="414.8" clip-path="url(#breeze-setup-regenerate-command-images-line-47)">workflow-run:sync-staging-to-main)</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1166.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-47)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1166.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-47)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1191.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-48)">│</text><text class="breeze-setup-regenerate-command-images-r4" x="24.4" y="1191.2" textLength="146.4" clip-path="url(#breeze-setup-regenerate-command-images-line-48)">--check-only</text><text class="breeze-setup-regenerate-command-images-r1" x="195.2" y="1191.2" textLength="1244.4" clip-path="url(#breeze-setup-regenerate-command-images-line-48)">Only&#160;check&#160;if&#160;some&#160;images&#160;need&#160;to&#160;be&#160;regenerated.&#160;Return&#160;0&#160;if&#160;no&#160;need&#160;or&#160;1&#160;if&#160;needed.&#160;Cannot&#160;be&#160;used&#160;&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1191.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-48)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1191.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-48)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1215.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-49)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="195.2" y="1215.6" textLength="170.8" clip-path="url(#breeze-setup-regenerate-command-images-line-49)">together&#160;with&#160;</text><text class="breeze-setup-regenerate-command-images-r4" x="366" y="1215.6" textLength="109.8" clip-path="url(#breeze-setup-regenerate-command-images-line-49)">--command</text><text class="breeze-setup-regenerate-command-images-r1" x="475.8" y="1215.6" textLength="109.8" clip-path="url(#breeze-setup-regenerate-command-images-line-49)">&#160;flag&#160;or&#160;</text><text class="breeze-setup-regenerate-command-images-r4" x="585.6" y="1215.6" textLength="85.4" clip-path="url(#breeze-setup-regenerate-command-images-line-49)">--force</text><text class="breeze-setup-regenerate-command-images-r1" x="671" y="1215.6" textLength="768.6" clip-path="url(#breeze-setup-regenerate-command-images-line-49)">.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1215.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-49)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1215.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-49)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1240" textLength="1464" clip-path="url(#breeze-setup-regenerate-command-images-line-50)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1240" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-50)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1264.4" textLength="24.4" clip-path="url(#breeze-setup-regenerate-command-images-line-51)">╭─</text><text class="breeze-setup-regenerate-command-images-r5" x="24.4" y="1264.4" textLength="195.2" clip-path="url(#breeze-setup-regenerate-command-images-line-51)">&#160;Common&#160;options&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="219.6" y="1264.4" textLength="1220" clip-path="url(#breeze-setup-regenerate-command-images-line-51)">────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="breeze-setup-regenerate-command-images-r5" x="1439.6" y="1264.4" textLength="24.4" clip-path="url(#breeze-setup-regenerate-command-images-line-51)">─╮</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1264.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-51)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1288.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-52)">│</text><text class="breeze-setup-regenerate-command-images-r4" x="24.4" y="1288.8" textLength="109.8" clip-path="url(#breeze-setup-regenerate-command-images-line-52)">--verbose</text><text class="breeze-setup-regenerate-command-images-r7" x="158.6" y="1288.8" textLength="24.4" clip-path="url(#breeze-setup-regenerate-command-images-line-52)">-v</text><text class="breeze-setup-regenerate-command-images-r1" x="207.4" y="1288.8" textLength="585.6" clip-path="url(#breeze-setup-regenerate-command-images-line-52)">Print&#160;verbose&#160;information&#160;about&#160;performed&#160;steps.</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1288.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-52)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1288.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-52)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1313.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-53)">│</text><text class="breeze-setup-regenerate-command-images-r4" x="24.4" y="1313.2" textLength="109.8" clip-path="url(#breeze-setup-regenerate-command-images-line-53)">--dry-run</text><text class="breeze-setup-regenerate-command-images-r7" x="158.6" y="1313.2" textLength="24.4" clip-path="url(#breeze-setup-regenerate-command-images-line-53)">-D</text><text class="breeze-setup-regenerate-command-images-r1" x="207.4" y="1313.2" textLength="719.8" clip-path="url(#breeze-setup-regenerate-command-images-line-53)">If&#160;dry-run&#160;is&#160;set,&#160;commands&#160;are&#160;only&#160;printed,&#160;not&#160;executed.</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1313.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-53)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1313.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-53)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1337.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-54)">│</text><text class="breeze-setup-regenerate-command-images-r4" x="24.4" y="1337.6" textLength="109.8" clip-path="url(#breeze-setup-regenerate-command-images-line-54)">--help&#160;&#160;&#160;</text><text class="breeze-setup-regenerate-command-images-r7" x="158.6" y="1337.6" textLength="24.4" clip-path="url(#breeze-setup-regenerate-command-images-line-54)">-h</text><text class="breeze-setup-regenerate-command-images-r1" x="207.4" y="1337.6" textLength="329.4" clip-path="url(#breeze-setup-regenerate-command-images-line-54)">Show&#160;this&#160;message&#160;and&#160;exit.</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1337.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-54)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1337.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-54)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1362" textLength="1464" clip-path="url(#breeze-setup-regenerate-command-images-line-55)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1362" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-55)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="239.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-9)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="239.6" textLength="1073.6" clip-path="url(#breeze-setup-regenerate-command-images-line-9)">ci-image:pull&#160;|&#160;ci-image:save&#160;|&#160;ci-image:verify&#160;|&#160;ci:approve-workflow-runs&#160;|&#160;ci:audit&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="239.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-9)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-9)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="264" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-10)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="264" textLength="1232.2" clip-path="url(#breeze-setup-regenerate-command-images-line-10)">ci:fix-ownership&#160;|&#160;ci:fork-ci-reconcile&#160;|&#160;ci:free-space&#160;|&#160;ci:get-workflow-info&#160;|&#160;ci:resource-check&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="264" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-10)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="264" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-10)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="288.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-11)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="288.4" textLength="1049.2" clip-path="url(#breeze-setup-regenerate-command-images-line-11)">ci:selective-check&#160;|&#160;ci:set-milestone&#160;|&#160;ci:upgrade&#160;|&#160;cleanup&#160;|&#160;doctor&#160;|&#160;down&#160;|&#160;exec&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="288.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-11)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-11)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="312.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-12)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="312.8" textLength="1244.4" clip-path="url(#breeze-setup-regenerate-command-images-line-12)">generate-migration-file&#160;|&#160;issues&#160;|&#160;issues:unassign&#160;|&#160;k8s&#160;|&#160;k8s:build-k8s-image&#160;|&#160;k8s:configure-cluster</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="312.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-12)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-12)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="337.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-13)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="337.2" textLength="1171.2" clip-path="url(#breeze-setup-regenerate-command-images-line-13)">|&#160;k8s:create-cluster&#160;|&#160;k8s:delete-cluster&#160;|&#160;k8s:deploy-airflow&#160;|&#160;k8s:deploy-cluster&#160;|&#160;k8s:dev&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="337.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-13)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-13)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="361.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-14)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="361.6" textLength="1220" clip-path="url(#breeze-setup-regenerate-command-images-line-14)">k8s:k9s&#160;|&#160;k8s:logs&#160;|&#160;k8s:run-complete-tests&#160;|&#160;k8s:setup-env&#160;|&#160;k8s:setup-lang-sdk-test&#160;|&#160;k8s:shell&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="361.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-14)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-14)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="386" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-15)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="386" textLength="1244.4" clip-path="url(#breeze-setup-regenerate-command-images-line-15)">k8s:smoke-test-overlay&#160;|&#160;k8s:status&#160;|&#160;k8s:tests&#160;|&#160;k8s:upload-k8s-image&#160;|&#160;prod-image&#160;|&#160;prod-image:build</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="386" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-15)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="386" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-15)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="410.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-16)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="410.4" textLength="1061.4" clip-path="url(#breeze-setup-regenerate-command-images-line-16)">|&#160;prod-image:load&#160;|&#160;prod-image:pull&#160;|&#160;prod-image:save&#160;|&#160;prod-image:verify&#160;|&#160;registry&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="410.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-16)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-16)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="434.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-17)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="434.8" textLength="1134.6" clip-path="url(#breeze-setup-regenerate-command-images-line-17)">registry:backfill&#160;|&#160;registry:extract-data&#160;|&#160;registry:publish-versions&#160;|&#160;release-management&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="434.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-17)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-17)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="459.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-18)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="459.2" textLength="1000.4" clip-path="url(#breeze-setup-regenerate-command-images-line-18)">release-management:add-back-references&#160;|&#160;release-management:check-release-files&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="459.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-18)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-18)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="483.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-19)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="483.6" textLength="1183.4" clip-path="url(#breeze-setup-regenerate-command-images-line-19)">release-management:classify-provider-changes&#160;|&#160;release-management:clean-old-provider-artifacts&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="483.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-19)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-19)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="508" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-20)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="508" textLength="1073.6" clip-path="url(#breeze-setup-regenerate-command-images-line-20)">release-management:constraints-version-check&#160;|&#160;release-management:create-minor-branch&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="508" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-20)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="508" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-20)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="532.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-21)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="532.4" textLength="1134.6" clip-path="url(#breeze-setup-regenerate-command-images-line-21)">release-management:generate-airflowctl-changelog&#160;|&#160;release-management:generate-constraints&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="532.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-21)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-21)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="556.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-22)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="556.8" textLength="1244.4" clip-path="url(#breeze-setup-regenerate-command-images-line-22)">release-management:generate-issue-content-airflow-ctl&#160;|&#160;release-management:generate-issue-content-core</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="556.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-22)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-22)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="581.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-23)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="581.2" textLength="695.4" clip-path="url(#breeze-setup-regenerate-command-images-line-23)">|&#160;release-management:generate-issue-content-helm-chart&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="581.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-23)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-23)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="605.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-24)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="605.6" textLength="1244.4" clip-path="url(#breeze-setup-regenerate-command-images-line-24)">release-management:generate-issue-content-providers&#160;|&#160;release-management:generate-providers-metadata&#160;|</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="605.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-24)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-24)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="630" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-25)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="630" textLength="1110.2" clip-path="url(#breeze-setup-regenerate-command-images-line-25)">release-management:install-provider-distributions&#160;|&#160;release-management:merge-prod-images&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="630" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-25)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="630" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-25)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="654.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-26)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="654.4" textLength="1159" clip-path="url(#breeze-setup-regenerate-command-images-line-26)">release-management:mirror-base-images&#160;|&#160;release-management:prepare-airflow-ctl-distributions&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="654.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-26)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-26)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="678.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-27)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="678.8" textLength="1207.8" clip-path="url(#breeze-setup-regenerate-command-images-line-27)">release-management:prepare-airflow-distributions&#160;|&#160;release-management:prepare-helm-chart-package&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="678.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-27)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-27)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="703.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-28)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="703.2" textLength="1171.2" clip-path="url(#breeze-setup-regenerate-command-images-line-28)">release-management:prepare-helm-chart-tarball&#160;|&#160;release-management:prepare-mypy-distributions&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="703.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-28)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-28)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="727.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-29)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="727.6" textLength="1244.4" clip-path="url(#breeze-setup-regenerate-command-images-line-29)">release-management:prepare-provider-distributions&#160;|&#160;release-management:prepare-provider-documentation&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="727.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-29)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-29)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="752" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-30)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="752" textLength="1000.4" clip-path="url(#breeze-setup-regenerate-command-images-line-30)">|&#160;release-management:prepare-python-client&#160;|&#160;release-management:prepare-tarball&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="752" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-30)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="752" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-30)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="776.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-31)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="776.4" textLength="1049.2" clip-path="url(#breeze-setup-regenerate-command-images-line-31)">release-management:prepare-task-sdk-distributions&#160;|&#160;release-management:publish-docs&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="776.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-31)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="776.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-31)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="800.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-32)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="800.8" textLength="1012.6" clip-path="url(#breeze-setup-regenerate-command-images-line-32)">release-management:publish-docs-to-s3&#160;|&#160;release-management:publish-schemas-to-s3&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="800.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-32)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="800.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-32)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="825.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-33)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="825.2" textLength="988.2" clip-path="url(#breeze-setup-regenerate-command-images-line-33)">release-management:release-prod-images&#160;|&#160;release-management:start-beta-process&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="825.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-33)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="825.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-33)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="849.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-34)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="849.6" textLength="890.6" clip-path="url(#breeze-setup-regenerate-command-images-line-34)">release-management:start-rc-process&#160;|&#160;release-management:start-release&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="849.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-34)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="849.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-34)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="874" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-35)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="874" textLength="915" clip-path="url(#breeze-setup-regenerate-command-images-line-35)">release-management:tag-providers&#160;|&#160;release-management:update-constraints&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="874" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-35)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="874" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-35)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="898.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-36)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="898.4" textLength="1244.4" clip-path="url(#breeze-setup-regenerate-command-images-line-36)">release-management:update-providers-next-version&#160;|&#160;release-management:verify-provider-distributions&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="898.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-36)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="898.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-36)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="922.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-37)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="922.8" textLength="1012.6" clip-path="url(#breeze-setup-regenerate-command-images-line-37)">release-management:verify-rc-by-pmc&#160;|&#160;run&#160;|&#160;sbom&#160;|&#160;sbom:build-all-airflow-images&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="922.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-37)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="922.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-37)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="947.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-38)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="947.2" textLength="927.2" clip-path="url(#breeze-setup-regenerate-command-images-line-38)">sbom:export-dependency-information&#160;|&#160;sbom:generate-providers-requirements&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="947.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-38)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="947.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-38)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="971.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-39)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="971.6" textLength="1159" clip-path="url(#breeze-setup-regenerate-command-images-line-39)">sbom:update-sbom-information&#160;|&#160;setup&#160;|&#160;setup:autocomplete&#160;|&#160;setup:check-all-params-in-groups&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="971.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-39)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="971.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-39)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="996" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-40)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="996" textLength="1244.4" clip-path="url(#breeze-setup-regenerate-command-images-line-40)">setup:config&#160;|&#160;setup:regenerate-command-images&#160;|&#160;setup:self-upgrade&#160;|&#160;setup:synchronize-local-mounts&#160;|</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="996" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-40)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="996" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-40)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1020.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-41)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="1020.4" textLength="1098" clip-path="url(#breeze-setup-regenerate-command-images-line-41)">setup:version&#160;|&#160;shell&#160;|&#160;start-airflow&#160;|&#160;testing&#160;|&#160;testing:airflow-ctl-integration-tests&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1020.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-41)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1020.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-41)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1044.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-42)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="1044.8" textLength="1085.8" clip-path="url(#breeze-setup-regenerate-command-images-line-42)">testing:airflow-ctl-tests&#160;|&#160;testing:airflow-e2e-tests&#160;|&#160;testing:core-integration-tests&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1044.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-42)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1044.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-42)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1069.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-43)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="1069.2" textLength="890.6" clip-path="url(#breeze-setup-regenerate-command-images-line-43)">testing:core-tests&#160;|&#160;testing:docker-compose-tests&#160;|&#160;testing:helm-tests&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1069.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-43)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1069.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-43)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1093.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-44)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="1093.6" textLength="1195.6" clip-path="url(#breeze-setup-regenerate-command-images-line-44)">testing:providers-integration-tests&#160;|&#160;testing:providers-tests&#160;|&#160;testing:python-api-client-tests&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1093.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-44)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1093.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-44)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1118" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-45)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="1118" textLength="1037" clip-path="url(#breeze-setup-regenerate-command-images-line-45)">testing:system-tests&#160;|&#160;testing:task-sdk-integration-tests&#160;|&#160;testing:task-sdk-tests&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1118" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-45)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1118" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-45)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1142.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-46)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="1142.4" textLength="1134.6" clip-path="url(#breeze-setup-regenerate-command-images-line-46)">testing:ui-e2e-tests&#160;|&#160;ui&#160;|&#160;ui:check-translation-completeness&#160;|&#160;ui:compile-assets&#160;|&#160;verify&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1142.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-46)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1142.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-46)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1166.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-47)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="1166.8" textLength="951.6" clip-path="url(#breeze-setup-regenerate-command-images-line-47)">workflow-run&#160;|&#160;workflow-run:publish-docs&#160;|&#160;workflow-run:release-constraints&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1166.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-47)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1166.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-47)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1191.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-48)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="1191.2" textLength="414.8" clip-path="url(#breeze-setup-regenerate-command-images-line-48)">workflow-run:sync-staging-to-main)</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1191.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-48)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1191.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-48)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1215.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-49)">│</text><text class="breeze-setup-regenerate-command-images-r4" x="24.4" y="1215.6" textLength="146.4" clip-path="url(#breeze-setup-regenerate-command-images-line-49)">--check-only</text><text class="breeze-setup-regenerate-command-images-r1" x="195.2" y="1215.6" textLength="1244.4" clip-path="url(#breeze-setup-regenerate-command-images-line-49)">Only&#160;check&#160;if&#160;some&#160;images&#160;need&#160;to&#160;be&#160;regenerated.&#160;Return&#160;0&#160;if&#160;no&#160;need&#160;or&#160;1&#160;if&#160;needed.&#160;Cannot&#160;be&#160;used&#160;&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1215.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-49)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1215.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-49)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1240" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-50)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="195.2" y="1240" textLength="170.8" clip-path="url(#breeze-setup-regenerate-command-images-line-50)">together&#160;with&#160;</text><text class="breeze-setup-regenerate-command-images-r4" x="366" y="1240" textLength="109.8" clip-path="url(#breeze-setup-regenerate-command-images-line-50)">--command</text><text class="breeze-setup-regenerate-command-images-r1" x="475.8" y="1240" textLength="109.8" clip-path="url(#breeze-setup-regenerate-command-images-line-50)">&#160;flag&#160;or&#160;</text><text class="breeze-setup-regenerate-command-images-r4" x="585.6" y="1240" textLength="85.4" clip-path="url(#breeze-setup-regenerate-command-images-line-50)">--force</text><text class="breeze-setup-regenerate-command-images-r1" x="671" y="1240" textLength="768.6" clip-path="url(#breeze-setup-regenerate-command-images-line-50)">.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1240" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-50)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1240" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-50)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1264.4" textLength="1464" clip-path="url(#breeze-setup-regenerate-command-images-line-51)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1264.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-51)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1288.8" textLength="24.4" clip-path="url(#breeze-setup-regenerate-command-images-line-52)">╭─</text><text class="breeze-setup-regenerate-command-images-r5" x="24.4" y="1288.8" textLength="195.2" clip-path="url(#breeze-setup-regenerate-command-images-line-52)">&#160;Common&#160;options&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="219.6" y="1288.8" textLength="1220" clip-path="url(#breeze-setup-regenerate-command-images-line-52)">────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="breeze-setup-regenerate-command-images-r5" x="1439.6" y="1288.8" textLength="24.4" clip-path="url(#breeze-setup-regenerate-command-images-line-52)">─╮</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1288.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-52)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1313.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-53)">│</text><text class="breeze-setup-regenerate-command-images-r4" x="24.4" y="1313.2" textLength="109.8" clip-path="url(#breeze-setup-regenerate-command-images-line-53)">--verbose</text><text class="breeze-setup-regenerate-command-images-r7" x="158.6" y="1313.2" textLength="24.4" clip-path="url(#breeze-setup-regenerate-command-images-line-53)">-v</text><text class="breeze-setup-regenerate-command-images-r1" x="207.4" y="1313.2" textLength="585.6" clip-path="url(#breeze-setup-regenerate-command-images-line-53)">Print&#160;verbose&#160;information&#160;about&#160;performed&#160;steps.</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1313.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-53)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1313.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-53)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1337.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-54)">│</text><text class="breeze-setup-regenerate-command-images-r4" x="24.4" y="1337.6" textLength="109.8" clip-path="url(#breeze-setup-regenerate-command-images-line-54)">--dry-run</text><text class="breeze-setup-regenerate-command-images-r7" x="158.6" y="1337.6" textLength="24.4" clip-path="url(#breeze-setup-regenerate-command-images-line-54)">-D</text><text class="breeze-setup-regenerate-command-images-r1" x="207.4" y="1337.6" textLength="719.8" clip-path="url(#breeze-setup-regenerate-command-images-line-54)">If&#160;dry-run&#160;is&#160;set,&#160;commands&#160;are&#160;only&#160;printed,&#160;not&#160;executed.</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1337.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-54)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1337.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-54)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1362" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-55)">│</text><text class="breeze-setup-regenerate-command-images-r4" x="24.4" y="1362" textLength="109.8" clip-path="url(#breeze-setup-regenerate-command-images-line-55)">--help&#160;&#160;&#160;</text><text class="breeze-setup-regenerate-command-images-r7" x="158.6" y="1362" textLength="24.4" clip-path="url(#breeze-setup-regenerate-command-images-line-55)">-h</text><text class="breeze-setup-regenerate-command-images-r1" x="207.4" y="1362" textLength="329.4" clip-path="url(#breeze-setup-regenerate-command-images-line-55)">Show&#160;this&#160;message&#160;and&#160;exit.</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1362" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-55)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1362" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-55)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1386.4" textLength="1464" clip-path="url(#breeze-setup-regenerate-command-images-line-56)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1386.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-56)">
</text>
</g>
</g>

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

@@ -1 +1 @@
3588c0e4f1fda167e7f07b6d1b7843de
17b27f3a6bfb16a9119e47440e68e7f3
@@ -26,6 +26,7 @@ import subprocess
import sys
import tempfile
from collections.abc import Iterable
from datetime import UTC, datetime, timedelta
from io import StringIO
from pathlib import Path
from typing import TYPE_CHECKING, Any, NamedTuple
@@ -1797,3 +1798,56 @@ def audit(fix: bool):
run_command(shlex.split(command), check=False)
failed = [result for result in report() if result.status == CheckStatus.FAIL]
sys.exit(1 if failed else 0)
@ci_group.command(
name="approve-workflow-runs",
help="Approve the waiting workflow runs of external contributors' PRs whose changes are safe "
"to run without a maintainer's look, and label the others 'need workflow approval'. "
"Used by the auto-approve-workflow-runs workflow.",
)
@click.option(
"--github-token",
help="GitHub token able to approve workflow runs and label PRs.",
envvar="GITHUB_TOKEN",
required=True,
)
@click.option(
"--max-run-age-hours",
help="Only decide on workflow runs created within this many hours; older waiting runs are left alone.",
type=click.IntRange(min=1),
default=24,
show_default=True,
envvar="MAX_RUN_AGE_HOURS",
)
@click.option(
"--report-only",
is_flag=True,
help="Only report the decisions and estimates, without approving runs or changing labels.",
envvar="REPORT_ONLY",
)
@option_github_repository
@option_verbose
@option_dry_run
def approve_workflow_runs(
github_token: str, max_run_age_hours: int, report_only: bool, github_repository: str
):
from airflow_breeze.utils.shared_options import get_dry_run
from airflow_breeze.utils.workflow_run_approval import (
GitHubApi,
process_waiting_runs,
write_step_summary,
)
api = GitHubApi(
github_repository=github_repository,
github_token=github_token,
report_only=report_only or get_dry_run(),
)
created_since = datetime.now(UTC) - timedelta(hours=max_run_age_hours)
results = process_waiting_runs(api, created_since=created_since)
write_step_summary(results, github_repository)
failed_prs = [result.pr_number for result in results if result.failed]
if failed_prs:
console_print(f"[error]Processing failed for PRs: {', '.join(f'#{n}' for n in failed_prs)}[/]")
sys.exit(1)
@@ -28,6 +28,7 @@ CI_COMMANDS: dict[str, str | list[str]] = {
"set-milestone",
"fork-ci-reconcile",
"audit",
"approve-workflow-runs",
],
}
CI_PARAMETERS: dict[str, list[dict[str, str | list[str]]]] = {
@@ -140,4 +141,20 @@ CI_PARAMETERS: dict[str, list[dict[str, str | list[str]]]] = {
],
},
],
"breeze ci approve-workflow-runs": [
{
"name": "Approval flags",
"options": [
"--max-run-age-hours",
"--report-only",
],
},
{
"name": "GitHub parameters",
"options": [
"--github-token",
"--github-repository",
],
},
],
}
@@ -0,0 +1,226 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""
Estimate the GitHub Actions minutes a ``Tests (AMD)`` PR run takes from its selective-check outputs.
The numbers are medians measured on recent PR runs on GitHub-hosted (public) runners, which is where
fork PRs run. They are estimates for deciding whether a change's CI run is cheap enough to approve
without a maintainer, not a bill: queueing, retries and cache misses are not modelled.
"""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from airflow_breeze.global_constants import all_selective_core_test_types
from airflow_breeze.utils.path_utils import AIRFLOW_ROOT_PATH
# TODO: refresh these estimates periodically with an agent, and always after changing the CI
# workflows (see .github/AGENTS.md). Run ``dev/calibrate_ci_minutes_estimates.py`` and copy the
# medians it prints into the constants below.
# Last calibrated: 2026-10-23, from 310 successful ``Tests (AMD)`` PR runs of 2026-10-13..2026-10-23,
# 224 of which tested a partial set of providers.
# TODO: add a second set of estimates for self-hosted (RunsOn) runners once enough runs on them exist
# to measure. Their machine sizes differ, so the public-runner numbers do not carry over.
# Each entry: (job-minutes, minutes the group adds to the run's critical path after the CI image).
BASIC_CHECKS_ONLY_ESTIMATE = (6, 3)
BASE_ESTIMATE = (47, 17)
DOCS_ESTIMATE = (15, 10)
# The PROD image waits for constraints, so its tests sit about 26 minutes after the CI image.
PROD_IMAGE_ESTIMATE = (51, 26)
KUBERNETES_ESTIMATE = (84, 29)
HELM_ESTIMATE = (55, 10)
KUSTOMIZE_ESTIMATE = (6, 6)
INTEGRATION_ESTIMATE = (5, 5)
SPECIAL_TESTS_ESTIMATE = (317, 32)
TASK_SDK_ESTIMATE = (6, 6)
AGENT_FRAMEWORK_ESTIMATE = (11, 5)
JAVA_SDK_ESTIMATE = (7, 4)
GO_SDK_ESTIMATE = (1, 1)
TS_SDK_ESTIMATE = (1, 1)
TS_SDK_DOCS_ESTIMATE = (1, 1)
# The provider mypy job runs whenever provider code changes, also without provider unit tests.
MYPY_PROVIDERS_ESTIMATE = (8, 8)
AIRFLOW_CTL_ESTIMATE = (1, 1)
# Core tests: about 38 job-minutes for the first test type and 61 for each further one, up to
# the cost of all core test types.
CORE_FIRST_TEST_TYPE_MINUTES = 38
CORE_EXTRA_TEST_TYPE_MINUTES = 61
CORE_ALL_TEST_TYPES_MINUTES = 222
CORE_LONGEST_JOB_MINUTES = {1: 11, 2: 29}
CORE_LONGEST_JOB_MINUTES_DEFAULT = 32
# Provider tests (DB/non-DB, lowest dependencies, compatibility, distributions) scale with the size of
# the tested providers' test suites: amazon or google cost far more than a small provider. Fitted on
# 224 runs testing 2 to 22 providers, together with the provider mypy job: job-minutes ~= 105 + 0.161
# per provider test file, and the longest provider job ~= 16 + 0.015 per test file, both capped at the
# cost of testing all providers. The mypy job's median is taken off the base and the all-providers cost.
PROVIDERS_BASE_MINUTES = 105 - MYPY_PROVIDERS_ESTIMATE[0]
PROVIDERS_MINUTES_PER_TEST_FILE = 0.161
PROVIDERS_ALL_MINUTES = 315 - MYPY_PROVIDERS_ESTIMATE[0]
PROVIDERS_LONGEST_JOB_BASE_MINUTES = 16
PROVIDERS_LONGEST_JOB_MINUTES_PER_TEST_FILE = 0.015
PROVIDERS_ALL_LONGEST_JOB_MINUTES = 36
# Build info, the CI image build, queueing and the final jobs before and after the parallel part.
RUN_OVERHEAD_MINUTES = 18
@dataclass
class EstimatedJobGroup:
name: str
job_minutes: int
critical_path_minutes: int
@dataclass
class CiMinutesEstimate:
job_groups: list[EstimatedJobGroup]
@property
def job_minutes(self) -> int:
return sum(group.job_minutes for group in self.job_groups)
@property
def wall_clock_minutes(self) -> int:
if not self.job_groups:
return 0
longest = max(group.critical_path_minutes for group in self.job_groups)
if self.job_groups[0].name == "Basic checks only":
return longest
return RUN_OVERHEAD_MINUTES + longest
def estimate_core_job_minutes(core_test_types: int) -> int:
if core_test_types == 0:
return 0
return min(
CORE_FIRST_TEST_TYPE_MINUTES + CORE_EXTRA_TEST_TYPE_MINUTES * (core_test_types - 1),
CORE_ALL_TEST_TYPES_MINUTES,
)
def count_provider_test_files(provider_id: str) -> int:
tests_path = AIRFLOW_ROOT_PATH / "providers" / Path(*provider_id.split(".")) / "tests"
return sum(1 for _ in tests_path.rglob("test_*.py"))
def find_tested_provider_ids(provider_test_types: list[str]) -> list[str] | None:
"""Return the ids of individually tested providers, or None when all providers are tested."""
provider_ids = []
for test_type in provider_test_types:
if not test_type.startswith("Providers[") or test_type.startswith("Providers[-"):
return None
provider_ids.extend(test_type[len("Providers[") : -1].split(","))
return provider_ids
def estimate_providers_group(provider_test_types: list[str]) -> EstimatedJobGroup | None:
if not provider_test_types:
return None
provider_ids = find_tested_provider_ids(provider_test_types)
if provider_ids is None:
return EstimatedJobGroup(
"Provider tests (all providers)", PROVIDERS_ALL_MINUTES, PROVIDERS_ALL_LONGEST_JOB_MINUTES
)
test_files = sum(count_provider_test_files(provider_id) for provider_id in provider_ids)
return EstimatedJobGroup(
f"Provider tests ({len(provider_ids)} providers, {test_files} test files)",
min(
round(PROVIDERS_BASE_MINUTES + PROVIDERS_MINUTES_PER_TEST_FILE * test_files),
PROVIDERS_ALL_MINUTES,
),
min(
round(
PROVIDERS_LONGEST_JOB_BASE_MINUTES + PROVIDERS_LONGEST_JOB_MINUTES_PER_TEST_FILE * test_files
),
PROVIDERS_ALL_LONGEST_JOB_MINUTES,
),
)
# Jobs that need only build-info, so they also run when no CI image is built. The Java SDK group
# includes its docs job.
IMAGE_INDEPENDENT_GROUPS = (
("run_java_sdk_tests", "Java SDK tests", JAVA_SDK_ESTIMATE),
("run_go_sdk_tests", "Go SDK tests", GO_SDK_ESTIMATE),
("run_ts_sdk_tests", "TypeScript SDK tests", TS_SDK_ESTIMATE),
("run_ts_sdk_docs", "TypeScript SDK docs", TS_SDK_DOCS_ESTIMATE),
("run_airflow_ctl_tests", "Airflow CTL tests", AIRFLOW_CTL_ESTIMATE),
)
def estimate_image_independent_groups(selective_checks: Any) -> list[EstimatedJobGroup]:
return [
EstimatedJobGroup(name, *estimate)
for flag, name, estimate in IMAGE_INDEPENDENT_GROUPS
if getattr(selective_checks, flag)
]
def estimate_ci_minutes(selective_checks: Any) -> CiMinutesEstimate:
"""Estimate the job groups, job-minutes and wall-clock time of a ``Tests (AMD)`` PR run."""
if selective_checks.basic_checks_only:
return CiMinutesEstimate(
[
EstimatedJobGroup("Basic checks only", *BASIC_CHECKS_ONLY_ESTIMATE),
*estimate_image_independent_groups(selective_checks),
]
)
groups = [EstimatedJobGroup("CI image, basic tests and static checks", *BASE_ESTIMATE)]
if selective_checks.docs_build:
groups.append(EstimatedJobGroup("Documentation build", *DOCS_ESTIMATE))
if selective_checks.run_unit_tests:
core_test_types = len(selective_checks._get_core_test_types_to_run())
if core_test_types:
all_types = core_test_types >= len(all_selective_core_test_types())
groups.append(
EstimatedJobGroup(
f"Core tests ({'all' if all_types else core_test_types} test types)",
estimate_core_job_minutes(core_test_types),
CORE_LONGEST_JOB_MINUTES.get(core_test_types, CORE_LONGEST_JOB_MINUTES_DEFAULT),
)
)
groups.append(EstimatedJobGroup("Integration and system tests", *INTEGRATION_ESTIMATE))
# Like the provider test jobs of ci-amd.yml, which all need run-unit-tests.
if selective_checks.run_unit_tests and not selective_checks.skip_providers_tests:
providers_group = estimate_providers_group(
selective_checks._get_providers_test_types_to_run(split_to_individual_providers=True)
)
if providers_group:
groups.append(providers_group)
if selective_checks.run_mypy_providers:
groups.append(EstimatedJobGroup("Provider mypy checks", *MYPY_PROVIDERS_ESTIMATE))
if selective_checks.full_tests_needed:
groups.append(EstimatedJobGroup("Special tests", *SPECIAL_TESTS_ESTIMATE))
optional_groups = (
("prod_image_build", "PROD image build and tests", PROD_IMAGE_ESTIMATE),
("run_kubernetes_tests", "Kubernetes tests", KUBERNETES_ESTIMATE),
("run_helm_tests", "Helm tests", HELM_ESTIMATE),
("run_kustomize_overlays_tests", "Kustomize overlays tests", KUSTOMIZE_ESTIMATE),
("run_task_sdk_tests", "Task SDK tests", TASK_SDK_ESTIMATE),
("run_agent_framework_tests", "Agent framework tests", AGENT_FRAMEWORK_ESTIMATE),
)
for flag, name, estimate in optional_groups:
if getattr(selective_checks, flag):
groups.append(EstimatedJobGroup(name, *estimate))
groups.extend(estimate_image_independent_groups(selective_checks))
return CiMinutesEstimate(groups)
@@ -214,6 +214,33 @@ def tamper_close_comment(*, run_url: str) -> str:
)
CI_CHANGE_MARKER = "<!-- workflow-approval: ci-change -->"
# A long list of contributor-chosen paths would bury the explanation.
MAX_LISTED_CI_FILES = 10
def _sanitize_path(path: str) -> str:
# File names are the contributor's; a backtick or a line break would end the code span and let
# them post Markdown, including mentions, as the bot.
return "".join(char if char.isprintable() and char != "`" else "?" for char in path)
def ci_change_approval_comment(*, ci_files: list[str]) -> str:
listed = [f"- `{_sanitize_path(path)}`" for path in ci_files[:MAX_LISTED_CI_FILES]]
if len(ci_files) > MAX_LISTED_CI_FILES:
listed.append(f"- and {len(ci_files) - MAX_LISTED_CI_FILES} more")
return (
f"{CI_CHANGE_MARKER}\n\n"
"**Your CI change needs a maintainer.** CI for this pull request runs in your fork, and the "
"workflow runs it starts in apache/airflow are approved automatically while they only run the "
"fork CI gate. This pull request changes files that define what CI runs:\n\n"
+ "\n".join(listed)
+ "\n\nSuch a change can make project CI run on the project's runners, so its workflow runs in "
"apache/airflow wait for a maintainer to review the change and approve them. CI in your fork "
"is not affected, and its result stays visible on this pull request. " + WHY_FORK_CI + FOOTER
)
_STATUS_DESCRIPTIONS = {
"setup_required": "Fork CI not set up - see the pull request comment",
"awaiting_push": "Fork CI enabled, waiting for a push - see the pull request comment",
@@ -0,0 +1,793 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""
Automatic approval of workflow runs from external contributors' fork PRs.
GitHub holds the workflow runs of a fork PR in ``action_required`` until someone with write access
approves them. This module approves the runs of PRs to ``main`` that do not touch anything that
defines CI or chooses runners, do not need the full set of tests or a dependency upgrade, and whose
jobs, as selected by selective checks, are estimated to take a limited number of job-minutes.
Everything else gets the ``need workflow approval`` label and waits for a maintainer.
A PR whose CI runs in the contributor's fork (AIP-120) is decided by one rule instead: its project
CI run only executes the fork CI gate, so it is approved whatever its size, unless it changes files
that define CI, in which case it waits for a maintainer and the author gets a comment saying why.
Only API metadata and git objects of the PR are read. The PR's code is never checked out or run:
selective checks come from the trusted ``main`` checkout and only see the list of changed files
and the PR's ``pyproject.toml`` contents through ``git show``.
"""
from __future__ import annotations
import os
import re
import subprocess
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any
import requests
from rich.markup import escape
from airflow_breeze.global_constants import GithubEvents
from airflow_breeze.utils.ci_minutes_estimate import CiMinutesEstimate, estimate_ci_minutes
from airflow_breeze.utils.console import console_print
from airflow_breeze.utils.fork_ci_gate import (
GATED_REASONS,
LABEL_USE_FORK_CI,
LABEL_USE_PROJECT_CI,
GateConfig,
GateEvent,
decide,
load_config,
parse_github_datetime,
)
from airflow_breeze.utils.fork_ci_messages import CI_CHANGE_MARKER, ci_change_approval_comment
from airflow_breeze.utils.fork_ci_reconcile import changes_ci_definition
from airflow_breeze.utils.path_utils import AIRFLOW_ROOT_PATH
from airflow_breeze.utils.run_utils import run_command
from airflow_breeze.utils.selective_checks import (
ALL_VERSIONS_LABEL,
AREA_E2E_TESTS_LABEL,
AREA_KUBERNETES_TESTS_LABEL,
CANARY_LABEL,
DEFAULT_VERSIONS_ONLY_LABEL,
FULL_TESTS_NEEDED_LABEL,
LATEST_VERSIONS_ONLY_LABEL,
UPGRADE_TO_NEWER_DEPENDENCIES_LABEL,
)
NEED_WORKFLOW_APPROVAL_LABEL = "need workflow approval"
# Only PRs to main are approved automatically. PRs to release branches (``v3-4-test``,
# ``airflow-ctl/v0-1-test``, ...) run those branches' own, older workflows and always need a maintainer.
AUTO_APPROVABLE_BASE_BRANCH = "main"
MAX_ERROR_TEXT_LENGTH = 200
# Changes here can change which runner a job uses (``runs-on:``, ``runner-type`` computed by
# selective checks, the scripts build-info runs) or what the workflows do, so they always need a
# maintainer's look.
CI_DEFINITION_PATH_PREFIXES = (".github/", "dev/breeze/", "scripts/ci/")
# Dependency manifests and lock files. Selective checks only switch to dependency-upgrade mode for
# pyproject.toml, but a changed lock file or JS / Go / Java manifest also changes what CI installs.
DEPENDENCY_FILE_PATTERN = re.compile(
r"(^|/)(pyproject\.toml|uv\.lock|requirements[^/]*\.txt|package\.json|pnpm-lock\.yaml"
r"|package-lock\.json|yarn\.lock|go\.mod|go\.sum|build\.gradle(\.kts)?|settings\.gradle(\.kts)?"
r"|gradle\.lockfile|libs\.versions\.toml|pom\.xml"
r"|gradle-wrapper\.(properties|jar)|maven-wrapper\.(properties|jar)|gradlew(\.bat)?|mvnw(\.cmd)?)$"
)
# About 23% of a full-tests run (~1330 job-minutes on public runners), just above the cluster of
# single core areas and the Helm chart (274-301). Changes to large providers (amazon 364, google 374)
# and anything bigger wait for a maintainer, to keep the load on the shared public runners down.
MAX_AUTO_APPROVED_JOB_MINUTES = 310
# Larger changes to non-test code are features or behaviour changes rather than fixes, and get a
# maintainer's look even when their CI is cheap. 100 lines is the 90th percentile of the PRs that
# pass the other rules; above it the distribution thins out into feature-sized changes.
MAX_AUTO_APPROVED_SOURCE_LINES = 100
# Files that are not counted as source: tests (including e2e tests), docs, newsfragments,
# translations, lock files and images.
NON_SOURCE_FILE_PATTERN = re.compile(
r"(^|/)(tests?|test_utils|testing|[^/]*e2e[^/]*)/"
r"|(^|/)test_[^/]*$|_test\.(py|go)$|\.test\.(ts|tsx|js)$|\.spec\.(ts|tsx)$|Test\.java$"
r"|\.(md|rst|txt)$|(^|/)newsfragments/|(^|/)docs?/"
r"|/i18n/locales/|uv\.lock$|pnpm-lock\.yaml$|package-lock\.json$|go\.sum$"
r"|\.(svg|png|jpg|snap)$"
)
GITHUB_API_URL = "https://api.github.com"
# Labels with which selective checks select a different set of tests or versions. Selective checks
# read the labels of the run's event, so a run started before one was added or removed still runs
# the selection of the labels it started with, which the current labels do not show.
TEST_SELECTION_LABELS = frozenset(
{
ALL_VERSIONS_LABEL,
AREA_E2E_TESTS_LABEL,
AREA_KUBERNETES_TESTS_LABEL,
CANARY_LABEL,
DEFAULT_VERSIONS_ONLY_LABEL,
FULL_TESTS_NEEDED_LABEL,
LATEST_VERSIONS_ONLY_LABEL,
UPGRADE_TO_NEWER_DEPENDENCIES_LABEL,
}
)
# Labels that move a PR between project CI and fork CI. The gate reads them when it runs, but falls
# back to the labels of the run's event when it cannot, so a run started before one changed could
# still run project CI.
GATE_OVERRIDE_LABELS = frozenset({LABEL_USE_FORK_CI, LABEL_USE_PROJECT_CI})
# Read from the trusted main checkout, which is also the base branch whose gate job decides the
# PRs that are approved automatically.
FORK_CI_GATE_CONFIG_PATH = AIRFLOW_ROOT_PATH / ".github" / "fork-ci-gate.toml"
@dataclass
class ChangedFile:
path: str
changed_lines: int
@dataclass
class ApprovalDecision:
approve: bool
reasons: list[str] = field(default_factory=list)
estimate: CiMinutesEstimate | None = None
# Posted on the PR once, when the runs are left for a maintainer.
comment: str | None = None
# The labels the decision depends on: runs started before one of them changed are not approved.
decision_labels: frozenset[str] = TEST_SELECTION_LABELS
def count_source_lines(changed_files: list[ChangedFile]) -> int:
return sum(file.changed_lines for file in changed_files if not NON_SOURCE_FILE_PATTERN.search(file.path))
def find_ci_definition_files(files: list[str]) -> list[str]:
return [file for file in files if file.startswith(CI_DEFINITION_PATH_PREFIXES)]
def find_dependency_files(files: list[str]) -> list[str]:
return [file for file in files if DEPENDENCY_FILE_PATTERN.search(file)]
def find_new_provider_files(files: list[str]) -> list[str]:
"""
Return provider.yaml files that the trusted checkout does not have.
Selective checks run from main's tree and do not know a provider the PR adds, so they cannot
estimate its tests.
"""
return [
file
for file in files
if file.startswith("providers/")
and file.endswith("/provider.yaml")
and not (AIRFLOW_ROOT_PATH / file).exists()
]
def is_gated_to_fork_ci(*, pr: dict[str, Any], github_repository: str, config: GateConfig) -> bool:
"""Whether the PR's ``Tests (AMD)`` run only executes the fork CI gate (AIP-120)."""
event = GateEvent(
event_name="pull_request",
repository=github_repository,
ref="",
head_repository=pr["head"]["repo"]["full_name"] if pr["head"].get("repo") else "",
author_login=(pr.get("user") or {}).get("login") or "",
author_association=pr.get("author_association") or "",
created_at=parse_github_datetime(pr["created_at"]),
labels=tuple(label["name"] for label in pr.get("labels", [])),
)
return decide(event, config).reason in GATED_REASONS
def find_fork_ci_definition_files(files: list[str]) -> list[str]:
"""The files the fork CI reconciler counts as defining CI, so both decide alike."""
return [file for file in files if changes_ci_definition([file], file_count=1)]
def decide_gated_approval(*, changed_files: list[ChangedFile]) -> ApprovalDecision:
"""
Decide on the runs of a PR whose CI runs in the contributor's fork.
Its project CI run only executes the gate, so its cost and size do not matter. A change to the CI
definition could remove the gate or add a workflow that does run, so it still needs a maintainer,
and the author is told why.
"""
ci_files = find_fork_ci_definition_files([file.path for file in changed_files])
if ci_files:
return ApprovalDecision(
approve=False,
reasons=[f"its CI runs in the fork, but it changes CI definition files: {', '.join(ci_files)}"],
comment=ci_change_approval_comment(ci_files=ci_files),
)
return ApprovalDecision(
approve=True,
reasons=["its CI runs in the contributor's fork, so its project CI run only executes the gate"],
decision_labels=GATE_OVERRIDE_LABELS,
)
def decide_approval(
*,
base_branch: str,
changed_files: list[ChangedFile],
selective_checks: Any,
) -> ApprovalDecision:
"""Decide whether the workflow runs of a fork PR commit can be approved without a maintainer."""
try:
full_tests_needed = selective_checks.full_tests_needed
all_versions = selective_checks.all_versions
is_canary_run = selective_checks._is_canary_run()
upgrade_to_newer_dependencies = selective_checks.upgrade_to_newer_dependencies
estimate = estimate_ci_minutes(selective_checks)
except (SystemExit, Exception):
# Selective checks exit when a PR changes a suspended provider without the label allowing it,
# and raise on malformed PR content such as an invalid pyproject.toml. Either way, fail closed.
return ApprovalDecision(approve=False, reasons=["selective checks fail for this change"])
if base_branch != AUTO_APPROVABLE_BASE_BRANCH:
return ApprovalDecision(
approve=False,
reasons=[f"it targets {base_branch}, not {AUTO_APPROVABLE_BASE_BRANCH}"],
estimate=estimate,
)
paths = [file.path for file in changed_files]
ci_files = find_ci_definition_files(paths)
if ci_files:
return ApprovalDecision(
approve=False,
reasons=[f"it changes CI definition files: {', '.join(ci_files)}"],
estimate=estimate,
)
dependency_files = find_dependency_files(paths)
if dependency_files:
return ApprovalDecision(
approve=False,
reasons=[f"it changes dependency files: {', '.join(dependency_files)}"],
estimate=estimate,
)
new_provider_files = find_new_provider_files(paths)
if new_provider_files:
return ApprovalDecision(
approve=False,
reasons=[f"it adds a new provider: {', '.join(new_provider_files)}"],
estimate=estimate,
)
if full_tests_needed:
return ApprovalDecision(approve=False, reasons=["it needs the full set of tests"], estimate=estimate)
if is_canary_run:
# The canary label adds special tests and integration jobs that the estimate does not model.
return ApprovalDecision(approve=False, reasons=["it runs as a canary build"], estimate=estimate)
if all_versions:
# The estimate models the default Python and database versions only.
return ApprovalDecision(
approve=False, reasons=["it runs all Python and database versions"], estimate=estimate
)
if upgrade_to_newer_dependencies:
return ApprovalDecision(approve=False, reasons=["it changes dependencies"], estimate=estimate)
if estimate.job_minutes > MAX_AUTO_APPROVED_JOB_MINUTES:
return ApprovalDecision(
approve=False,
reasons=[
f"its jobs take an estimated {estimate.job_minutes} job-minutes "
f"(more than {MAX_AUTO_APPROVED_JOB_MINUTES})"
],
estimate=estimate,
)
source_lines = count_source_lines(changed_files)
if source_lines > MAX_AUTO_APPROVED_SOURCE_LINES:
return ApprovalDecision(
approve=False,
reasons=[
f"it changes {source_lines} lines of source code "
f"(more than {MAX_AUTO_APPROVED_SOURCE_LINES}, tests and docs not counted)"
],
estimate=estimate,
)
return ApprovalDecision(
approve=True,
reasons=[
f"its jobs take an estimated {estimate.job_minutes} job-minutes "
f"(at most {MAX_AUTO_APPROVED_JOB_MINUTES}) and it changes {source_lines} lines of source code"
],
estimate=estimate,
)
def parse_github_time(value: str) -> datetime:
return datetime.fromisoformat(value.replace("Z", "+00:00"))
class GitHubApi:
def __init__(self, *, github_repository: str, github_token: str, report_only: bool):
self.github_repository = github_repository
self.report_only = report_only
self._timelines: dict[int, list[dict[str, Any]]] = {}
self._session = requests.Session()
self._session.headers.update(
{
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {github_token}",
"X-GitHub-Api-Version": "2022-11-28",
}
)
def _get_paginated(self, path: str, *, item_key: str | None = None) -> list[dict[str, Any]]:
items: list[dict[str, Any]] = []
url: str | None = f"{GITHUB_API_URL}/repos/{self.github_repository}/{path}"
while url:
response = self._session.get(url, timeout=60)
response.raise_for_status()
payload = response.json()
items.extend(payload[item_key] if item_key else payload)
url = response.links.get("next", {}).get("url")
return items
def get_runs_waiting_for_approval(self, created_since: datetime) -> list[dict[str, Any]]:
created = created_since.strftime("%Y-%m-%dT%H:%M:%SZ")
return self._get_paginated(
f"actions/runs?status=action_required&event=pull_request&per_page=100&created=%3E%3D{created}",
item_key="workflow_runs",
)
def get_open_pull_requests(self) -> list[dict[str, Any]]:
return self._get_paginated("pulls?state=open&per_page=100")
def approve_run(self, run_id: int) -> None:
if self.report_only:
console_print(f"[info]Report only: would approve workflow run {run_id}[/]")
return
response = self._session.post(
f"{GITHUB_API_URL}/repos/{self.github_repository}/actions/runs/{run_id}/approve", timeout=60
)
response.raise_for_status()
def add_label(self, pr_number: int, label: str) -> None:
if self.report_only:
console_print(f"[info]Report only: would add label '{label}' to PR #{pr_number}[/]")
return
response = self._session.post(
f"{GITHUB_API_URL}/repos/{self.github_repository}/issues/{pr_number}/labels",
json={"labels": [label]},
timeout=60,
)
response.raise_for_status()
def has_bot_comment(self, pr_number: int, marker: str) -> bool:
comments = self._get_paginated(f"issues/{pr_number}/comments?per_page=100")
# Only the workflow's own comments count; anyone can post a comment containing the marker.
return any(
(comment.get("user") or {}).get("login") == "github-actions[bot]" and marker in comment["body"]
for comment in comments
)
def create_comment(self, pr_number: int, body: str) -> None:
if self.report_only:
console_print(f"[info]Report only: would comment on PR #{pr_number}[/]")
return
response = self._session.post(
f"{GITHUB_API_URL}/repos/{self.github_repository}/issues/{pr_number}/comments",
json={"body": body},
timeout=60,
)
response.raise_for_status()
def _get_timeline(self, pr_number: int) -> list[dict[str, Any]]:
if pr_number not in self._timelines:
self._timelines[pr_number] = self._get_paginated(f"issues/{pr_number}/timeline?per_page=100")
return self._timelines[pr_number]
def get_last_base_change(self, pr_number: int) -> datetime | None:
changes = [
parse_github_time(event["created_at"])
for event in self._get_timeline(pr_number)
if event.get("event") == "base_ref_changed"
]
return max(changes, default=None)
def get_last_label_change(self, pr_number: int, labels: frozenset[str]) -> datetime | None:
"""When one of ``labels`` was last added to or removed from the PR."""
changes = [
parse_github_time(event["created_at"])
for event in self._get_timeline(pr_number)
if event.get("event") in ("labeled", "unlabeled")
and (event.get("label") or {}).get("name") in labels
]
return max(changes, default=None)
def remove_label(self, pr_number: int, label: str) -> None:
if self.report_only:
console_print(f"[info]Report only: would remove label '{label}' from PR #{pr_number}[/]")
return
response = self._session.delete(
f"{GITHUB_API_URL}/repos/{self.github_repository}/issues/{pr_number}/labels/{label}",
timeout=60,
)
if response.status_code != 404:
response.raise_for_status()
def is_shallow_repository() -> bool:
result = run_command(
["git", "rev-parse", "--is-shallow-repository"],
check=False,
capture_output=True,
text=True,
dry_run_override=False,
)
return result.stdout.strip() == "true"
def fetch_merge_commit(*, github_repository: str, pr_number: int, head_sha: str) -> str | None:
"""
Fetch the PR's merge commit (what the CI run tests) and return its sha.
Return None when there is no merge commit or it was not built from ``head_sha``, so the result
is always tied to the commit whose runs get approved.
"""
ref = f"refs/remotes/auto-approve/pull/{pr_number}/merge"
# Only the merge commit and its parents are needed. Limit the depth only in an already shallow
# checkout (CI): in a full clone, --depth would record shallow boundaries and truncate history.
depth = ["--depth=2"] if is_shallow_repository() else []
result = run_command(
[
"git",
"fetch",
"--no-tags",
*depth,
f"https://github.com/{github_repository}.git",
f"+refs/pull/{pr_number}/merge:{ref}",
],
check=False,
capture_output=True,
text=True,
dry_run_override=False,
)
if result.returncode != 0:
return None
parents = run_command(
["git", "rev-list", "--parents", "-n", "1", ref],
check=False,
capture_output=True,
text=True,
dry_run_override=False,
)
if parents.returncode != 0:
return None
merge_sha, *parent_shas = parents.stdout.split()
if len(parent_shas) != 2 or parent_shas[1] != head_sha:
return None
return merge_sha
NUMSTAT_RECORD = re.compile(r"(\d+|-)\t(\d+|-)\t(.+)", re.DOTALL)
def get_changed_files(merge_sha: str) -> list[ChangedFile]:
"""
List the files the merge commit changes, with their changed line counts.
Paths must be literal for the CI-definition and source checks: without ``-z`` git C-quotes unusual
file names, and rename detection reports ``old => new`` instead of both paths.
"""
result = run_command(
[
"git",
"-c",
"core.quotePath=false",
"diff",
"--no-renames",
"--numstat",
"-z",
f"{merge_sha}^1",
merge_sha,
],
check=True,
capture_output=True,
text=True,
dry_run_override=False,
)
changed_files = []
for record in result.stdout.split("\0"):
if not record:
continue
match = NUMSTAT_RECORD.fullmatch(record)
if match is None:
raise ValueError(f"Unexpected git numstat record: {record!r}")
added, deleted, path = match.groups()
# Binary files report "-" for both counts; count them over the limit so they never pass.
changed_lines = int(added) + int(deleted) if added != "-" else MAX_AUTO_APPROVED_SOURCE_LINES + 1
changed_files.append(ChangedFile(path=path, changed_lines=changed_lines))
return changed_files
def build_selective_checks(
*, merge_sha: str, changed_files: list[ChangedFile], pr_labels: tuple[str, ...], base_branch: str
):
from airflow_breeze.utils.selective_checks import SelectiveChecks
# Release-branch PRs select different jobs (no provider tests, for example), so selective checks
# must see the branch the PR targets.
return SelectiveChecks(
files=tuple(file.path for file in changed_files),
default_branch=base_branch,
commit_ref=merge_sha,
pr_labels=pr_labels,
github_event=GithubEvents.PULL_REQUEST,
)
@dataclass
class PullRequestResult:
pr_number: int
head_sha: str
run_ids: list[int]
decision: ApprovalDecision
failed: bool = False
def process_waiting_runs(api: GitHubApi, *, created_since: datetime) -> list[PullRequestResult]:
"""
Decide on the runs created since ``created_since``.
Older waiting runs are left alone, so the workflow never approves a backlog of stale runs at once.
"""
runs = api.get_runs_waiting_for_approval(created_since)
if not runs:
console_print(
f"[info]No workflow runs created since {created_since:%Y-%m-%d %H:%M} UTC wait for approval.[/]"
)
return []
runs_by_head: dict[tuple[str, str, str], list[dict[str, Any]]] = {}
for run in runs:
head_repository = (run.get("head_repository") or {}).get("full_name", "")
if head_repository == api.github_repository:
continue
runs_by_head.setdefault((head_repository, run["head_branch"], run["head_sha"]), []).append(run)
# The runs of fork PRs do not list their pull request, so match them by head repository, branch
# and commit.
pull_requests: dict[tuple[str, str, str], list[dict[str, Any]]] = {}
for pr in api.get_open_pull_requests():
head_repository = pr["head"]["repo"]["full_name"] if pr["head"].get("repo") else ""
pull_requests.setdefault((head_repository, pr["head"]["ref"], pr["head"]["sha"]), []).append(pr)
results = []
for (head_repository, head_branch, head_sha), head_runs in runs_by_head.items():
run_ids = [run["id"] for run in head_runs]
matching_prs = pull_requests.get((head_repository, head_branch, head_sha), [])
if not matching_prs:
console_print(
f"[warning]No open PR has head {escape(head_repository)}:{escape(head_branch)}@{head_sha}; "
f"leaving runs {run_ids} alone[/]"
)
continue
for pr in matching_prs:
failed = False
decision: ApprovalDecision | None = None
try:
if len(matching_prs) > 1:
# The runs cannot be told apart by PR, so one PR's decision must not approve them all.
decision = ApprovalDecision(
approve=False, reasons=["its head commit is the head of several open PRs"]
)
else:
decision = decide_for_pull_request(api=api, pr=pr)
pr_run_ids = run_ids
if decision.approve:
pr_run_ids = find_runs_started_for_current_pr(
api=api, pr=pr, runs=head_runs, decision_labels=decision.decision_labels
)
if not pr_run_ids:
decision = ApprovalDecision(
approve=False,
reasons=[
"its waiting runs started before the PR was opened, retargeted "
"or changed a label its decision depends on"
],
estimate=decision.estimate,
)
pr_run_ids = run_ids
apply_decision(api=api, pr=pr, run_ids=pr_run_ids, decision=decision)
except (requests.RequestException, subprocess.CalledProcessError) as e:
# One failing PR must not stop the others; the failure is reported through ``failed``.
failed = True
console_print(f"[warning]PR #{pr['number']}: processing failed: {escape(str(e))}[/]")
pr_run_ids = run_ids
# Without the label a PR that keeps failing drops out of the query window unnoticed.
try:
if not has_need_workflow_approval_label(pr):
api.add_label(pr["number"], NEED_WORKFLOW_APPROVAL_LABEL)
except requests.RequestException as label_error:
console_print(
f"[warning]PR #{pr['number']}: adding the label failed: {escape(str(label_error))}[/]"
)
decision = ApprovalDecision(
approve=False,
reasons=[build_failure_reason(e)],
estimate=decision.estimate if decision else None,
)
results.append(
PullRequestResult(
pr_number=pr["number"],
head_sha=head_sha,
run_ids=pr_run_ids,
decision=decision,
failed=failed,
)
)
return results
def find_runs_started_for_current_pr(
*,
api: GitHubApi,
pr: dict[str, Any],
runs: list[dict[str, Any]],
decision_labels: frozenset[str] = TEST_SELECTION_LABELS,
) -> list[int]:
"""
Return the runs started after the PR was opened and after its base branch last changed.
Retargeting a PR does not start new runs, and a closed PR can leave runs with the same head behind.
Such runs were started for another base branch, so the decision about the current PR must not
approve them.
:param decision_labels: the labels the decision depends on. It is made from the current labels,
while a run can act on those of its event: with ``full tests needed`` removed, or
``default versions only`` added to a run of all versions, the estimate is smaller than the
run, and without ``use fork ci`` in its event a run can fall back to project CI.
"""
started_after = parse_github_time(pr["created_at"])
changes = [
api.get_last_base_change(pr["number"]),
api.get_last_label_change(pr["number"], decision_labels),
]
started_after = max([started_after, *(change for change in changes if change is not None)])
return [run["id"] for run in runs if parse_github_time(run["created_at"]) >= started_after]
def decide_for_pull_request(*, api: GitHubApi, pr: dict[str, Any]) -> ApprovalDecision:
merge_sha = fetch_merge_commit(
github_repository=api.github_repository, pr_number=pr["number"], head_sha=pr["head"]["sha"]
)
if merge_sha is None:
return ApprovalDecision(
approve=False, reasons=["its merge commit is missing or was not built from the PR's head commit"]
)
try:
changed_files = get_changed_files(merge_sha)
except ValueError:
return ApprovalDecision(approve=False, reasons=["its changed files could not be listed"])
if pr["base"]["ref"] == AUTO_APPROVABLE_BASE_BRANCH and is_gated_to_fork_ci(
pr=pr, github_repository=api.github_repository, config=load_config(FORK_CI_GATE_CONFIG_PATH)
):
return decide_gated_approval(changed_files=changed_files)
pr_labels = tuple(label["name"] for label in pr.get("labels", []))
return decide_approval(
base_branch=pr["base"]["ref"],
changed_files=changed_files,
selective_checks=build_selective_checks(
merge_sha=merge_sha,
changed_files=changed_files,
pr_labels=pr_labels,
base_branch=pr["base"]["ref"],
),
)
def build_failure_reason(error: Exception) -> str:
text = " ".join(str(error).split())
if len(text) > MAX_ERROR_TEXT_LENGTH:
# The end of a failed git command holds its exit status, so keep both ends.
half = (MAX_ERROR_TEXT_LENGTH - len("...")) // 2
text = f"{text[:half]}...{text[-half:]}"
reason = f"processing failed with {type(error).__name__}"
return f"{reason}: {text}" if text else reason
def has_need_workflow_approval_label(pr: dict[str, Any]) -> bool:
return any(label["name"] == NEED_WORKFLOW_APPROVAL_LABEL for label in pr.get("labels", []))
def apply_decision(
*, api: GitHubApi, pr: dict[str, Any], run_ids: list[int], decision: ApprovalDecision
) -> None:
pr_number = pr["number"]
has_label = has_need_workflow_approval_label(pr)
console_print(f"[info]PR #{pr_number}: {format_estimate(decision.estimate)}[/]")
if decision.approve:
console_print(
f"[success]Approving runs {run_ids} of PR #{pr_number}: {escape('; '.join(decision.reasons))}[/]"
)
for run_id in run_ids:
api.approve_run(run_id)
if has_label:
api.remove_label(pr_number, NEED_WORKFLOW_APPROVAL_LABEL)
return
# Reasons can contain contributor-chosen file names, which must not be read as Rich markup.
console_print(f"[warning]PR #{pr_number} needs a maintainer: {escape('; '.join(decision.reasons))}[/]")
if not has_label:
api.add_label(pr_number, NEED_WORKFLOW_APPROVAL_LABEL)
# Once per PR: every later push would otherwise repeat it while the change stays.
if decision.comment is not None and not api.has_bot_comment(pr_number, CI_CHANGE_MARKER):
api.create_comment(pr_number, decision.comment)
MARKDOWN_SPECIAL_CHARACTERS = re.compile(r"([\\`*_{}\[\]()<>#+!|~])")
def escape_markdown(text: str) -> str:
"""Escape Markdown in contributor-controlled text, such as file names, for the job summary."""
return MARKDOWN_SPECIAL_CHARACTERS.sub(r"\\\1", text)
def format_estimate(estimate: CiMinutesEstimate | None) -> str:
if estimate is None:
return "n/a"
return f"~{estimate.job_minutes} job-min, ~{estimate.wall_clock_minutes} min wall clock"
def write_step_summary(results: list[PullRequestResult], github_repository: str) -> None:
summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
if not summary_path:
return
lines = [
"## Workflow run approval",
"",
"Estimates are for `Tests (AMD)` on public runners.",
"",
"| PR | Head | Decision | Estimated CI time | Reasons |",
"|---|---|---|---|---|",
]
for result in results:
if result.failed:
decision = "failed"
elif result.decision.approve:
decision = "approved"
else:
decision = f"`{NEED_WORKFLOW_APPROVAL_LABEL}`"
lines.append(
f"| [#{result.pr_number}](https://github.com/{github_repository}/pull/{result.pr_number}) "
f"| `{result.head_sha[:10]}` | {decision} | {format_estimate(result.decision.estimate)} "
f"| {escape_markdown('; '.join(result.decision.reasons))} |"
)
for result in results:
estimate = result.decision.estimate
if estimate is None:
continue
lines.extend(
[
"",
f"### #{result.pr_number}: expected jobs",
"",
"| Job group | Estimated job-minutes | Critical path (min) |",
"|---|---|---|",
*(
f"| {group.name} | {group.job_minutes} | {group.critical_path_minutes} |"
for group in estimate.job_groups
),
f"| **Total** | **{estimate.job_minutes}** | **~{estimate.wall_clock_minutes} wall clock** |",
]
)
with open(summary_path, "a") as summary_file:
summary_file.write("\n".join(lines) + "\n")
@@ -0,0 +1,101 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from __future__ import annotations
import importlib.util
import json
import pytest
from airflow_breeze.utils.path_utils import AIRFLOW_ROOT_PATH
# The script is standalone (run with `uv run`), so it is loaded from its path.
SCRIPT_PATH = AIRFLOW_ROOT_PATH / "dev" / "calibrate_ci_minutes_estimates.py"
_spec = importlib.util.spec_from_file_location("calibrate_ci_minutes_estimates", SCRIPT_PATH)
assert _spec is not None
assert _spec.loader is not None
calibrate = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(calibrate)
JOBS = [
{
"name": "Basic tests / Static checks",
"conclusion": "success",
"started_at": "2026-10-22T10:00:00Z",
"completed_at": "2026-10-22T10:06:00Z",
},
{
"name": "MyPy providers checks",
"conclusion": "success",
"started_at": "2026-10-22T10:00:00Z",
"completed_at": "2026-10-22T10:08:00Z",
},
{
"name": "TypeScript SDK docs",
"conclusion": "success",
"started_at": "2026-10-22T10:00:00Z",
"completed_at": "2026-10-22T10:01:00Z",
},
]
def _cache_run(cache_dir, run_id, *, runner_type='["ubuntu-22.04"]', all_versions="false"):
(cache_dir / f"{run_id}.jobs.json").write_text(json.dumps(JOBS))
(cache_dir / f"{run_id}.build-info.log").write_text(
"2026-10-22T10:00:00Z full-tests-needed = false\n"
f"2026-10-22T10:00:00Z runner-type = {runner_type}\n"
f"2026-10-22T10:00:00Z all-versions = {all_versions}\n"
)
@pytest.mark.parametrize(
("runner_type", "all_versions", "counted"),
[
pytest.param('["ubuntu-22.04"]', "false", True, id="public runners, default versions"),
pytest.param(
"runs-on=1/family=m7a/image=ubuntu22-full-x64/volume=150gb/cpu={0}/ram={1}",
"false",
False,
id="self-hosted runners",
),
pytest.param('["ubuntu-22.04"]', "true", False, id="all versions"),
],
)
def test_build_rows_counts_only_the_runs_the_estimate_models(tmp_path, runner_type, all_versions, counted):
_cache_run(tmp_path, 1, runner_type=runner_type, all_versions=all_versions)
rows = calibrate.build_rows([1], tmp_path)
assert len(rows) == int(counted)
def test_build_rows_groups_ts_sdk_docs_separately(tmp_path):
_cache_run(tmp_path, 1)
(row,) = calibrate.build_rows([1], tmp_path)
assert row["minutes"]["ts-sdk-docs"] == 1
assert "ts-sdk" not in row["minutes"]
def test_build_rows_groups_provider_mypy_separately(tmp_path):
_cache_run(tmp_path, 1)
(row,) = calibrate.build_rows([1], tmp_path)
assert row["minutes"]["mypy-providers"] == 8
assert "providers" not in row["minutes"]
@@ -0,0 +1,229 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from __future__ import annotations
from unittest import mock
import pytest
from airflow_breeze.utils.ci_minutes_estimate import (
BASE_ESTIMATE,
BASIC_CHECKS_ONLY_ESTIMATE,
CORE_ALL_TEST_TYPES_MINUTES,
MYPY_PROVIDERS_ESTIMATE,
PROVIDERS_ALL_LONGEST_JOB_MINUTES,
PROVIDERS_ALL_MINUTES,
RUN_OVERHEAD_MINUTES,
TS_SDK_DOCS_ESTIMATE,
estimate_ci_minutes,
estimate_core_job_minutes,
estimate_providers_group,
find_tested_provider_ids,
)
from airflow_breeze.utils.selective_checks import SelectiveChecks
OPTIONAL_FLAGS = (
"docs_build",
"run_unit_tests",
"full_tests_needed",
"prod_image_build",
"run_kubernetes_tests",
"run_helm_tests",
"run_kustomize_overlays_tests",
"run_task_sdk_tests",
"run_agent_framework_tests",
"run_java_sdk_tests",
"run_go_sdk_tests",
"run_ts_sdk_tests",
"run_ts_sdk_docs",
"run_airflow_ctl_tests",
"run_mypy_providers",
)
def _selective_checks(
*, core_test_types=(), provider_test_types=(), basic_checks_only=False, **flags
) -> mock.MagicMock:
selective_checks = mock.MagicMock(spec=SelectiveChecks)
selective_checks.basic_checks_only = basic_checks_only
selective_checks.skip_providers_tests = not provider_test_types
for flag in OPTIONAL_FLAGS:
setattr(selective_checks, flag, flags.get(flag, False))
selective_checks._get_core_test_types_to_run.return_value = list(core_test_types)
selective_checks._get_providers_test_types_to_run.return_value = list(provider_test_types)
return selective_checks
@pytest.mark.parametrize(
("core_test_types", "expected"),
[(0, 0), (1, 38), (2, 99), (3, 160), (6, CORE_ALL_TEST_TYPES_MINUTES)],
)
def test_estimate_core_job_minutes(core_test_types, expected):
assert estimate_core_job_minutes(core_test_types) == expected
@pytest.mark.parametrize(
("provider_test_types", "expected"),
[
(["Providers[amazon]", "Providers[google]"], ["amazon", "google"]),
(["Providers[common.compat,fab]"], ["common.compat", "fab"]),
(["Providers[-amazon,google]", "Providers[amazon]"], None),
(["Providers"], None),
],
)
def test_find_tested_provider_ids(provider_test_types, expected):
assert find_tested_provider_ids(provider_test_types) == expected
@mock.patch("airflow_breeze.utils.ci_minutes_estimate.count_provider_test_files", autospec=True)
def test_estimate_providers_group_weights_providers_by_test_files(mock_count):
mock_count.side_effect = lambda provider_id: {"fab": 50, "amazon": 500}[provider_id]
small = estimate_providers_group(["Providers[fab]"])
large = estimate_providers_group(["Providers[amazon]"])
assert small.name == "Provider tests (1 providers, 50 test files)"
assert small.job_minutes < large.job_minutes
assert small.critical_path_minutes < large.critical_path_minutes
def test_estimate_providers_group_for_all_providers():
group = estimate_providers_group(["Providers"])
assert (group.job_minutes, group.critical_path_minutes) == (
PROVIDERS_ALL_MINUTES,
PROVIDERS_ALL_LONGEST_JOB_MINUTES,
)
def test_estimate_providers_group_without_providers():
assert estimate_providers_group([]) is None
def test_estimate_basic_checks_only():
estimate = estimate_ci_minutes(_selective_checks(basic_checks_only=True))
assert [group.name for group in estimate.job_groups] == ["Basic checks only"]
assert estimate.job_minutes == BASIC_CHECKS_ONLY_ESTIMATE[0]
assert estimate.wall_clock_minutes == BASIC_CHECKS_ONLY_ESTIMATE[1]
@pytest.mark.parametrize(
("flags", "expected_groups"),
[
pytest.param(
{"run_ts_sdk_docs": True}, ["Basic checks only", "TypeScript SDK docs"], id="ts-sdk docs"
),
pytest.param(
{"run_java_sdk_tests": True, "run_go_sdk_tests": True, "run_airflow_ctl_tests": True},
["Basic checks only", "Java SDK tests", "Go SDK tests", "Airflow CTL tests"],
id="sdk tests",
),
],
)
def test_estimate_basic_checks_only_counts_jobs_that_need_no_ci_image(flags, expected_groups):
estimate = estimate_ci_minutes(_selective_checks(basic_checks_only=True, **flags))
assert [group.name for group in estimate.job_groups] == expected_groups
def test_estimate_counts_ts_sdk_docs_with_the_ci_image():
estimate = estimate_ci_minutes(_selective_checks(run_ts_sdk_docs=True))
assert estimate.job_groups[-1].name == "TypeScript SDK docs"
assert estimate.job_minutes == BASE_ESTIMATE[0] + TS_SDK_DOCS_ESTIMATE[0]
@mock.patch("airflow_breeze.utils.ci_minutes_estimate.count_provider_test_files", return_value=100)
def test_estimate_lists_the_job_groups_that_run(mock_count):
estimate = estimate_ci_minutes(
_selective_checks(
core_test_types=["Always"],
provider_test_types=["Providers[http]"],
docs_build=True,
run_unit_tests=True,
prod_image_build=True,
run_helm_tests=True,
)
)
assert [group.name for group in estimate.job_groups] == [
"CI image, basic tests and static checks",
"Documentation build",
"Core tests (1 test types)",
"Integration and system tests",
"Provider tests (1 providers, 100 test files)",
"PROD image build and tests",
"Helm tests",
]
assert estimate.job_minutes == sum(group.job_minutes for group in estimate.job_groups)
assert estimate.wall_clock_minutes == RUN_OVERHEAD_MINUTES + max(
group.critical_path_minutes for group in estimate.job_groups
)
def test_estimate_full_tests_adds_special_tests_and_all_core_types():
estimate = estimate_ci_minutes(
_selective_checks(
core_test_types=["API", "Always", "CLI", "Core", "Other", "Serialization"],
provider_test_types=["Providers"],
run_unit_tests=True,
full_tests_needed=True,
)
)
names = [group.name for group in estimate.job_groups]
assert "Core tests (all test types)" in names
assert "Provider tests (all providers)" in names
assert "Special tests" in names
def test_estimate_without_unit_tests_has_only_base_group():
estimate = estimate_ci_minutes(_selective_checks())
assert estimate.job_minutes == BASE_ESTIMATE[0]
@pytest.mark.parametrize(
("run_unit_tests", "expected_groups"),
[
pytest.param(
False, ["CI image, basic tests and static checks", "Provider mypy checks"], id="metadata only"
),
pytest.param(
True,
[
"CI image, basic tests and static checks",
"Integration and system tests",
"Provider tests (1 providers, 100 test files)",
"Provider mypy checks",
],
id="with unit tests",
),
],
)
@mock.patch("airflow_breeze.utils.ci_minutes_estimate.count_provider_test_files", return_value=100)
def test_estimate_charges_provider_tests_only_with_unit_tests(mock_count, run_unit_tests, expected_groups):
estimate = estimate_ci_minutes(
_selective_checks(
provider_test_types=["Providers[google]"], run_unit_tests=run_unit_tests, run_mypy_providers=True
)
)
assert [group.name for group in estimate.job_groups] == expected_groups
if not run_unit_tests:
assert estimate.job_minutes == BASE_ESTIMATE[0] + MYPY_PROVIDERS_ESTIMATE[0]
+18
View File
@@ -25,12 +25,15 @@ from airflow_breeze.utils.fork_ci_messages import (
_STATE_TEXTS,
_STATUS_DESCRIPTIONS,
BEHIND_WARNING_COMMITS,
CI_CHANGE_MARKER,
CI_PASSED_DESCRIPTION,
CI_PASSED_REVOKED_DESCRIPTION,
FOOTER,
JOB_NAME_MAX_LENGTH,
JOB_STATUS_CONTEXT_PREFIX,
MAX_LISTED_CI_FILES,
Marker,
ci_change_approval_comment,
inactivity_close_comment,
job_status_context,
nudge_comment,
@@ -143,3 +146,18 @@ def test_status_descriptions_fit_github_limit():
)
def test_job_status_context(name, expected):
assert job_status_context(name) == f"{JOB_STATUS_CONTEXT_PREFIX}{expected}"
def test_ci_change_approval_comment_lists_the_files():
paths = [f".github/workflows/w{i}.yml" for i in range(MAX_LISTED_CI_FILES + 2)]
comment = ci_change_approval_comment(
ci_files=["dev/breeze/`evil`.py", ".github/a\n\n@someone.yml", *paths]
)
assert comment.startswith(CI_CHANGE_MARKER)
assert comment.endswith(FOOTER)
assert "- `dev/breeze/?evil?.py`" in comment
assert "- `.github/a??@someone.yml`" in comment
assert "\n@someone" not in comment
assert "- and 4 more" in comment
assert f"`{paths[-1]}`" not in comment
File diff suppressed because it is too large. Load diff
+270
View File
@@ -0,0 +1,270 @@
#!/usr/bin/env python3
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# /// script
# requires-python = ">=3.11"
# ///
"""
Measure the numbers behind ``dev/breeze/src/airflow_breeze/utils/ci_minutes_estimate.py``.
Downloads recent successful ``Tests (AMD)`` PR runs, their job timings and the selective-check outputs
their ``Build info`` job printed, then prints the medians and fits to copy into the estimate module.
Only runs on public runners with the default versions are counted, as the estimate models them.
Downloads are cached in ``--cache-dir``, so re-runs only fetch new runs.
Needs an authenticated ``gh`` CLI. Run from the repository root:
uv run dev/calibrate_ci_minutes_estimates.py --since 2026-09-10
"""
from __future__ import annotations
import argparse
import json
import re
import statistics
import subprocess
from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime
from pathlib import Path
REPOSITORY = "apache/airflow"
WORKFLOW = "ci-amd.yml"
ANSI_ESCAPE = re.compile(r"\x1b\[[0-9;]*m")
# What ci_minutes_estimate.py models: jobs on GitHub's public AMD runners, default versions only.
# Self-hosted (RunsOn) runs and all-versions runs take different times and would skew the medians.
PUBLIC_AMD_RUNNERS = '["ubuntu-22.04"]'
# Job-name patterns for each job group of ci_minutes_estimate.py; the first match wins.
JOB_GROUPS = [
("docs", r"^CI image checks / (Build documentation|Publish documentation)"),
("core", r"^(MySQL|Postgres|Sqlite|Non-DB) tests: core|^Low dep tests:core"),
("special", r"^Special tests"),
("mypy-providers", r"^MyPy providers checks"),
("providers", r"^Provider distributions tests|tests: providers|^Low dep tests: providers"),
("prod", r"^Build PROD images|^Additional PROD image tests"),
("kubernetes", r"^Kubernetes tests"),
("helm", r"^Helm tests"),
("kustomize", r"^Kustomize"),
("integration", r"^Integration and System Tests"),
("task-sdk", r"^Task SDK tests"),
("agent-framework", r"^Agent framework tests"),
("java-sdk", r"^Java SDK"),
("go-sdk", r"^Go SDK"),
("ts-sdk-docs", r"^TypeScript SDK docs"),
("ts-sdk", r"^TypeScript SDK"),
("airflow-ctl", r"^Airflow CTL"),
("base", r".*"),
]
def gh_api(path: str, *extra: str) -> str:
for _ in range(3):
result = subprocess.run(["gh", "api", *extra, path], capture_output=True, text=True, check=False)
if result.returncode == 0:
return result.stdout
raise RuntimeError(f"gh api {path} failed: {result.stderr.strip()}")
def list_runs(since: str) -> list[int]:
run_ids: list[int] = []
for page in range(1, 11):
payload = json.loads(
gh_api(
f"repos/{REPOSITORY}/actions/workflows/{WORKFLOW}/runs?event=pull_request"
f"&status=success&per_page=100&page={page}&created=>={since}"
)
)
run_ids.extend(run["id"] for run in payload["workflow_runs"])
if len(payload["workflow_runs"]) < 100:
break
return run_ids
def fetch_run(run_id: int, cache_dir: Path) -> None:
jobs_file = cache_dir / f"{run_id}.jobs.json"
log_file = cache_dir / f"{run_id}.build-info.log"
try:
if not jobs_file.exists():
jobs = json.loads(
gh_api(f"repos/{REPOSITORY}/actions/runs/{run_id}/jobs?per_page=100", "--paginate", "--slurp")
)
jobs_file.write_text(json.dumps([job for page in jobs for job in page["jobs"]]))
if not log_file.exists():
build_info = next(job for job in json.loads(jobs_file.read_text()) if job["name"] == "Build info")
log_file.write_text(
gh_api(f"repos/{REPOSITORY}/actions/jobs/{build_info['id']}/logs", "--allow-escape-sequences")
)
except (RuntimeError, StopIteration) as error:
print(f"skipping run {run_id}: {error}")
def is_incomplete_json(value: str) -> bool:
if not value.startswith(("[", "{")):
return False
try:
json.loads(value)
except json.JSONDecodeError:
return True
return False
def read_outputs(log_file: Path) -> dict[str, str]:
# Rich wraps long JSON values over several log lines; glue continuation lines onto the value only
# until it parses, so diagnostics printed after it are not appended.
outputs: dict[str, str] = {}
key = None
for line in log_file.read_text(errors="replace").splitlines():
text = ANSI_ESCAPE.sub("", line).split(" ", 1)[-1]
match = re.match(r"^([a-z0-9-]+) = (.*)$", text)
if match:
key = match.group(1)
outputs[key] = match.group(2)
elif key and text and is_incomplete_json(outputs[key]):
outputs[key] += text
else:
key = None
return outputs
def get_test_types(outputs: dict[str, str], name: str) -> list[str]:
value = outputs.get(name)
if not value or value in ("null", "[]"):
return []
return sorted({test_type for group in json.loads(value) for test_type in group["test_types"].split()})
def count_provider_test_files(provider_id: str) -> int:
tests_path = Path("providers", *provider_id.split("."), "tests")
return sum(1 for _ in tests_path.rglob("test_*.py"))
def get_unmodelled_reason(outputs: dict[str, str]) -> str | None:
"""Why a run cannot calibrate the estimate, or None when it matches what the estimate models."""
if outputs.get("runner-type") != PUBLIC_AMD_RUNNERS:
return f"it ran on {outputs.get('runner-type', 'unknown runners')}"
if outputs.get("all-versions") != "false":
return "it ran all Python and database versions"
return None
def build_rows(run_ids: list[int], cache_dir: Path) -> list[dict]:
rows = []
for run_id in run_ids:
jobs_file = cache_dir / f"{run_id}.jobs.json"
log_file = cache_dir / f"{run_id}.build-info.log"
if not jobs_file.exists() or not log_file.exists():
continue
outputs = read_outputs(log_file)
if "full-tests-needed" not in outputs:
continue
reason = get_unmodelled_reason(outputs)
if reason is not None:
print(f"skipping run {run_id}: {reason}")
continue
minutes: dict[str, float] = defaultdict(float)
longest: dict[str, float] = defaultdict(float)
for job in json.loads(jobs_file.read_text()):
if job["conclusion"] == "skipped" or not job["started_at"] or not job["completed_at"]:
continue
duration = (
datetime.fromisoformat(job["completed_at"]) - datetime.fromisoformat(job["started_at"])
).total_seconds() / 60
group = next(group for group, pattern in JOB_GROUPS if re.search(pattern, job["name"]))
minutes[group] += duration
longest[group] = max(longest[group], duration)
try:
providers = get_test_types(outputs, "individual-providers-test-types-list-as-strings-in-json")
core_types = len(get_test_types(outputs, "core-test-types-list-as-strings-in-json"))
except json.JSONDecodeError:
print(f"skipping run {run_id}: its selective-check outputs could not be parsed")
continue
rows.append(
{
"outputs": outputs,
"minutes": minutes,
"longest": longest,
"core_types": core_types,
"providers": [p[len("Providers[") : -1] for p in providers if p.startswith("Providers[")],
}
)
return rows
def print_report(rows: list[dict]) -> None:
print(f"\nruns analysed: {len(rows)}")
print("\njob group runs median job-minutes median longest job")
for group, _ in JOB_GROUPS:
present = [row for row in rows if row["minutes"].get(group)]
if present:
print(
f"{group:18s}{len(present):5d} {statistics.median(r['minutes'][group] for r in present):18.1f}"
f" {statistics.median(r['longest'][group] for r in present):18.1f}"
)
print("\ncore test types runs median job-minutes median longest job")
core = [row for row in rows if row["core_types"] and row["minutes"].get("core")]
for count in sorted({row["core_types"] for row in core}):
selected = [row for row in core if row["core_types"] == count]
print(
f"{count:15d}{len(selected):7d} {statistics.median(r['minutes']['core'] for r in selected):18.1f}"
f" {statistics.median(r['longest']['core'] for r in selected):18.1f}"
)
tested = [row for row in rows if row["providers"] and row["minutes"].get("providers")]
partial = [row for row in tested if len(row["providers"]) < 50]
full = [row for row in tested if len(row["providers"]) >= 50]
if full:
print(
f"\nall providers: {len(full)} runs, median job-minutes "
f"{statistics.median(r['minutes']['providers'] for r in full):.1f}, median longest job "
f"{statistics.median(r['longest']['providers'] for r in full):.1f}"
)
test_files = [sum(count_provider_test_files(p) for p in row["providers"]) for row in partial]
if len(set(test_files)) < 2:
print("partial provider runs: not enough different provider test-suite sizes to fit")
else:
slope, intercept = statistics.linear_regression(
test_files, [r["minutes"]["providers"] for r in partial]
)
longest_slope, longest_intercept = statistics.linear_regression(
test_files, [r["longest"]["providers"] for r in partial]
)
print(
f"partial provider runs: {len(partial)} runs, "
f"{len({tuple(row['providers']) for row in partial})} distinct provider sets"
)
print(f" job-minutes ~= {intercept:.1f} + {slope:.3f} * provider test files")
print(f" longest job ~= {longest_intercept:.1f} + {longest_slope:.3f} * provider test files")
def main() -> None:
parser = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument("--since", required=True, help="Earliest run creation date (YYYY-MM-DD).")
parser.add_argument("--cache-dir", type=Path, default=Path("files/ci-minutes-cache"))
args = parser.parse_args()
args.cache_dir.mkdir(parents=True, exist_ok=True)
run_ids = list_runs(args.since)
print(f"runs listed: {len(run_ids)}")
with ThreadPoolExecutor(max_workers=4) as executor:
list(executor.map(lambda run_id: fetch_run(run_id, args.cache_dir), run_ids))
print_report(build_rows(run_ids, args.cache_dir))
if __name__ == "__main__":
main()