This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch optimize-image-wait-verify
in repository https://gitbox.apache.org/repos/asf/airflow.git

commit 0d0a5a768835b99fd7887dc53637183d9b256de0
Author: Jarek Potiuk <ja...@potiuk.com>
AuthorDate: Sat Nov 25 14:05:23 2023 +0100

    Optimize CI/PROD image waiting and verification in CI workflow
    
    Currently both "wait-for-ci-images" and  "preview-constraints"
    jobs are waiting for images to be built - which means that they
    both take a running worker slot (public runner) just to do
    the waiting while the image is being built. Also "verify-image"
    job is run as part of "wait-for-image" which adds additional
    delay between being downloaded and dependent jobs starting.
    
    This PR optimizes it quite a bit:
    
    * preview-constraints job now depends on "wait-for-ci-images".
      This means that only one slot will be busy while waiting
      for images.
    
    * both CI and PROD `verify-image` commands in breeze got
      --run-in-parallel set of flags that allow the verification
      to happen for all images in parallel.
    
    * there are separate jobs started that verifies the images
      (both for CI and PR images) - this way the job that waits
      for images can finish a but earlier and allow the dependent
      jobs to start in parallell to verifying the image.
    
    * In case of the "in-workflow-build" the "wait-for-ci-images"
      does not have to be run at all, because there wait-for-ci-images
      depends on in-workflow build-ci-images job - so if that job
      completes, we know image is built already and we do not have to
      wait for it separately - so far we had to run it in order to
      add `--verify` flag to verify the images. With separate job we
      can run i  in parallel to all the other waiting jobs.
    
    * Also names and dependencies between jobs are updated, including
      CI documentation describing diagrams of how CI workflows work.
      The diagrams are cleaned-up/verified and updated. The separate
      diagram for scheduled build has been removed as it was essentially
      the same as "canary build". A paragraph description for every
      type of workflow was added to add more context to the diagrams.
---
 .github/workflows/ci.yml                           | 378 +++++++++++---------
 CI.rst                                             |  60 ++--
 CI_DIAGRAMS.md                                     | 383 +++++++++++----------
 .../airflow_breeze/commands/ci_image_commands.py   | 123 +++++--
 .../commands/ci_image_commands_config.py           |  11 +
 .../commands/production_image_commands.py          | 131 +++++--
 .../commands/production_image_commands_config.py   |  11 +
 dev/breeze/src/airflow_breeze/utils/run_tests.py   |   2 +-
 docker_tests/test_ci_image.py                      |  34 +-
 images/breeze/output_ci-image_verify.svg           |  68 +++-
 images/breeze/output_ci-image_verify.txt           |   2 +-
 images/breeze/output_prod-image_verify.svg         |  68 +++-
 images/breeze/output_prod-image_verify.txt         |   2 +-
 13 files changed, 817 insertions(+), 456 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 11256ab1de..01bcdc6bab 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -44,7 +44,7 @@ env:
   CONSTRAINTS_GITHUB_REPOSITORY: >-
     ${{ secrets.CONSTRAINTS_GITHUB_REPOSITORY != '' &&
         secrets.CONSTRAINTS_GITHUB_REPOSITORY || 'apache/airflow' }}
-  # In builds from forks, this token is read-only. For scheduler/direct push 
it is WRITE one
+  # In builds from forks, this token is read-only. For scheduled/direct push 
it is WRITE one
   GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
   IMAGE_TAG: "${{ github.event.pull_request.head.sha || github.sha }}"
   USE_SUDO: "true"
@@ -320,148 +320,12 @@ jobs:
           PYTHON_VERSIONS: 
${{needs.build-info.outputs.all-python-versions-list-as-string}}
           DEBUG_RESOURCES: ${{needs.build-info.outputs.debug-resources}}
           BUILD_TIMEOUT_MINUTES: 70
-
-  generate-constraints:
-    permissions:
-      contents: read
-    timeout-minutes: 70
-    name: >
-      Preview constraints
-      ${{needs.build-info.outputs.all-python-versions-list-as-string}}
-    runs-on: ${{fromJSON(needs.build-info.outputs.runs-on)}}
-    needs: [build-info, build-ci-images]
-    env:
-      RUNS_ON: "${{ needs.build-info.outputs.runs-on }}"
-      PYTHON_VERSIONS: 
${{needs.build-info.outputs.all-python-versions-list-as-string}}
-      DEBUG_RESOURCES: ${{needs.build-info.outputs.debug-resources}}
-    if: needs.build-info.outputs.ci-image-build == 'true'
-    steps:
-      - name: Cleanup repo
-        run: docker run -v "${GITHUB_WORKSPACE}:/workspace" -u 0:0 bash -c "rm 
-rf /workspace/*"
-      - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
-        uses: actions/checkout@v4
-        with:
-          persist-credentials: false
-      - name: "Install Breeze"
-        uses: ./.github/actions/breeze
-      - name: Pull CI images ${{ env.PYTHON_VERSIONS }}:${{ env.IMAGE_TAG }}
-        run: breeze ci-image pull --run-in-parallel --tag-as-latest 
--wait-for-image
-      - name: "Source constraints"
-        shell: bash
-        run: >
-          breeze release-management generate-constraints --run-in-parallel
-          --airflow-constraints-mode constraints-source-providers
-      - name: "No providers constraints"
-        shell: bash
-        timeout-minutes: 25
-        run: >
-          breeze release-management generate-constraints --run-in-parallel
-          --airflow-constraints-mode constraints-no-providers
-      - name: "PyPI constraints"
-        shell: bash
-        timeout-minutes: 25
-        run: >
-          breeze release-management generate-constraints --run-in-parallel
-          --airflow-constraints-mode constraints
-      - name: "Dependency upgrade summary"
-        shell: bash
-        run: |
-          for PYTHON_VERSION in ${{ env.PYTHON_VERSIONS }}; do
-            echo "Summarizing Python $PYTHON_VERSION"
-            cat "files/constraints-${PYTHON_VERSION}"/*.md >> 
$GITHUB_STEP_SUMMARY || true
-          done
-      - name: "Upload constraint artifacts"
-        uses: actions/upload-artifact@v3
-        with:
-          name: constraints
-          path: ./files/constraints-*/constraints-*.txt
-          retention-days: 7
-
-  build-prod-images:
-    timeout-minutes: 80
-    name: >
-      ${{needs.build-info.outputs.build-job-description}} PROD images
-      ${{needs.build-info.outputs.all-python-versions-list-as-string}}
-    runs-on: ${{fromJSON(needs.build-info.outputs.runs-on)}}
-    needs: [build-info, build-ci-images]
-    env:
-      DEFAULT_BRANCH: ${{ needs.build-info.outputs.default-branch }}
-      DEFAULT_CONSTRAINTS_BRANCH: ${{ 
needs.build-info.outputs.default-constraints-branch }}
-      RUNS_ON: "${{needs.build-info.outputs.runs-on}}"
-      BACKEND: sqlite
-      DOCKER_CACHE: ${{ needs.build-info.outputs.cache-directive }}
-      VERSION_SUFFIX_FOR_PYPI: "dev0"
-      DEBUG_RESOURCES: ${{needs.build-info.outputs.debug-resources}}
-      # Force more parallelism for build even on public images
-      PARALLELISM: 6
-    steps:
-      - name: Cleanup repo
-        run: docker run -v "${GITHUB_WORKSPACE}:/workspace" -u 0:0 bash -c "rm 
-rf /workspace/*"
-        if: needs.build-info.outputs.in-workflow-build == 'true'
-      - uses: actions/checkout@v4
-        with:
-          ref: ${{ needs.build-info.outputs.targetCommitSha }}
-          persist-credentials: false
-        if: needs.build-info.outputs.in-workflow-build == 'true'
-      - name: "Install Breeze"
-        uses: ./.github/actions/breeze
-        if: needs.build-info.outputs.in-workflow-build == 'true'
-      - name: >
-          Build PROD Images
-          
${{needs.build-info.outputs.all-python-versions-list-as-string}}:${{env.IMAGE_TAG}}
-        uses: ./.github/actions/build-prod-images
-        if: needs.build-info.outputs.in-workflow-build == 'true'
-        with:
-          build-provider-packages: ${{ needs.build-info.outputs.default-branch 
== 'main' }}
-        env:
-          UPGRADE_TO_NEWER_DEPENDENCIES: ${{ 
needs.build-info.outputs.upgrade-to-newer-dependencies }}
-          DOCKER_CACHE: ${{ needs.build-info.outputs.cache-directive }}
-          PYTHON_VERSIONS: 
${{needs.build-info.outputs.all-python-versions-list-as-string}}
-          DEBUG_RESOURCES: ${{ needs.build-info.outputs.debug-resources }}
-
-  build-prod-images-bullseye:
-    timeout-minutes: 80
-    name: >
-      ${{needs.build-info.outputs.build-job-description}} Bullseye PROD images
-      ${{needs.build-info.outputs.all-python-versions-list-as-string}}
-    runs-on: ${{fromJSON(needs.build-info.outputs.runs-on)}}
-    needs: [build-info, build-ci-images]
-    if: needs.build-info.outputs.canary-run == 'true'
-    env:
-      DEFAULT_BRANCH: ${{ needs.build-info.outputs.default-branch }}
-      DEFAULT_CONSTRAINTS_BRANCH: ${{ 
needs.build-info.outputs.default-constraints-branch }}
-      RUNS_ON: "${{needs.build-info.outputs.runs-on}}"
-      BACKEND: sqlite
-      DOCKER_CACHE: ${{ needs.build-info.outputs.cache-directive }}
-      VERSION_SUFFIX_FOR_PYPI: "dev0"
-      DEBUG_RESOURCES: ${{needs.build-info.outputs.debug-resources}}
-      # Force more parallelism for build even on public images
-      PARALLELISM: 6
-    steps:
-      - name: Cleanup repo
-        run: docker run -v "${GITHUB_WORKSPACE}:/workspace" -u 0:0 bash -c "rm 
-rf /workspace/*"
-      - uses: actions/checkout@v3
-        with:
-          ref: ${{ needs.build-info.outputs.targetCommitSha }}
-          persist-credentials: false
-          submodules: recursive
-      - name: "Install Breeze"
-        uses: ./.github/actions/breeze
-      - name: >
-          Build Bullseye PROD Images
-          
${{needs.build-info.outputs.all-python-versions-list-as-string}}:${{env.IMAGE_TAG}}
-        uses: ./.github/actions/build-prod-images
-        with:
-          build-provider-packages: ${{ needs.build-info.outputs.default-branch 
== 'main' }}
+      - name: Verify CI images ${{ env.PYTHON_VERSIONS }}:${{ env.IMAGE_TAG }}
+        run: breeze ci-image verify --run-in-parallel
         env:
-          UPGRADE_TO_NEWER_DEPENDENCIES: ${{ 
needs.build-info.outputs.upgrade-to-newer-dependencies }}
-          DOCKER_CACHE: ${{ needs.build-info.outputs.cache-directive }}
           PYTHON_VERSIONS: 
${{needs.build-info.outputs.all-python-versions-list-as-string}}
-          DEBUG_RESOURCES: ${{ needs.build-info.outputs.debug-resources }}
-          DEBIAN_VERSION: "bullseye"
-          # Do not override the "bookworm" image - just push a new bullseye 
image
-          # TODO: improve caching for that build
-          IMAGE_TAG: "bullseye-${{ github.event.pull_request.head.sha || 
github.sha }}"
+          DEBUG_RESOURCES: ${{needs.build-info.outputs.debug-resources}}
+        if: needs.build-info.outputs.in-workflow-build == 'true'
 
   run-breeze-tests:
     timeout-minutes: 10
@@ -565,6 +429,7 @@ jobs:
         with:
           fetch-depth: 2
           persist-credentials: false
+
   wait-for-ci-images:
     timeout-minutes: 120
     name: "Wait for CI images"
@@ -579,18 +444,107 @@ jobs:
     steps:
       - name: Cleanup repo
         run: docker run -v "${GITHUB_WORKSPACE}:/workspace" -u 0:0 bash -c "rm 
-rf /workspace/*"
+        if: needs.build-info.outputs.in-workflow-build == 'false'
       - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
         uses: actions/checkout@v4
         with:
           persist-credentials: false
+        if: needs.build-info.outputs.in-workflow-build == 'false'
       - name: "Install Breeze"
         uses: ./.github/actions/breeze
+        if: needs.build-info.outputs.in-workflow-build == 'false'
       - name: Wait for CI images ${{ env.PYTHON_VERSIONS }}:${{ env.IMAGE_TAG 
}}
         id: wait-for-images
-        run: breeze ci-image pull --run-in-parallel --verify --wait-for-image 
--tag-as-latest
+        run: breeze ci-image pull --run-in-parallel --wait-for-image 
--tag-as-latest
         env:
           PYTHON_VERSIONS: ${{ 
needs.build-info.outputs.python-versions-list-as-string }}
           DEBUG_RESOURCES: ${{needs.build-info.outputs.debug-resources}}
+        if: needs.build-info.outputs.in-workflow-build == 'false'
+
+  verify-ci-images:
+    timeout-minutes: 80
+    name: Verify CI images 
${{needs.build-info.outputs.all-python-versions-list-as-string}}
+    runs-on: ${{fromJSON(needs.build-info.outputs.runs-on)}}
+    needs: [build-info, wait-for-ci-images]
+    env:
+      DEFAULT_BRANCH: ${{ needs.build-info.outputs.default-branch }}
+      DEFAULT_CONSTRAINTS_BRANCH: ${{ 
needs.build-info.outputs.default-constraints-branch }}
+      RUNS_ON: "${{needs.build-info.outputs.runs-on}}"
+      VERSION_SUFFIX_FOR_PYPI: "dev0"
+    steps:
+      - name: Cleanup repo
+        run: docker run -v "${GITHUB_WORKSPACE}:/workspace" -u 0:0 bash -c "rm 
-rf /workspace/*"
+      - uses: actions/checkout@v4
+        with:
+          ref: ${{ needs.build-info.outputs.targetCommitSha }}
+          persist-credentials: false
+      - name: Pull CI images ${{ env.PYTHON_VERSIONS }}:${{ env.IMAGE_TAG }}
+        run: breeze ci-image pull --run-in-parallel --tag-as-latest
+        env:
+          # Force more parallelism for pull even on public images
+          PARALLELISM: 6
+      - name: Verify CI images ${{ env.PYTHON_VERSIONS }}:${{ env.IMAGE_TAG }}
+        run: breeze ci-image verify --run-in-parallel
+        env:
+          PYTHON_VERSIONS: 
${{needs.build-info.outputs.all-python-versions-list-as-string}}
+          DEBUG_RESOURCES: ${{needs.build-info.outputs.debug-resources}}
+
+  preview-constraints:
+    permissions:
+      contents: read
+    timeout-minutes: 70
+    name: >
+      Preview constraints
+      ${{needs.build-info.outputs.all-python-versions-list-as-string}}
+    runs-on: ${{fromJSON(needs.build-info.outputs.runs-on)}}
+    needs: [build-info, wait-for-ci-images]
+    env:
+      RUNS_ON: "${{ needs.build-info.outputs.runs-on }}"
+      PYTHON_VERSIONS: 
${{needs.build-info.outputs.all-python-versions-list-as-string}}
+      DEBUG_RESOURCES: ${{needs.build-info.outputs.debug-resources}}
+    if: needs.build-info.outputs.ci-image-build == 'true'
+    steps:
+      - name: Cleanup repo
+        run: docker run -v "${GITHUB_WORKSPACE}:/workspace" -u 0:0 bash -c "rm 
-rf /workspace/*"
+      - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
+        uses: actions/checkout@v4
+        with:
+          persist-credentials: false
+      - name: "Install Breeze"
+        uses: ./.github/actions/breeze
+      - name: Pull CI images ${{ env.PYTHON_VERSIONS }}:${{ env.IMAGE_TAG }}
+        run: breeze ci-image pull --run-in-parallel --tag-as-latest
+      - name: "Source constraints"
+        shell: bash
+        run: >
+          breeze release-management generate-constraints --run-in-parallel
+          --airflow-constraints-mode constraints-source-providers
+      - name: "No providers constraints"
+        shell: bash
+        timeout-minutes: 25
+        run: >
+          breeze release-management generate-constraints --run-in-parallel
+          --airflow-constraints-mode constraints-no-providers
+      - name: "PyPI constraints"
+        shell: bash
+        timeout-minutes: 25
+        run: >
+          breeze release-management generate-constraints --run-in-parallel
+          --airflow-constraints-mode constraints
+      - name: "Dependency upgrade summary"
+        shell: bash
+        run: |
+          for PYTHON_VERSION in ${{ env.PYTHON_VERSIONS }}; do
+            echo "Summarizing Python $PYTHON_VERSION"
+            cat "files/constraints-${PYTHON_VERSION}"/*.md >> 
$GITHUB_STEP_SUMMARY || true
+          done
+      - name: "Upload constraint artifacts"
+        uses: actions/upload-artifact@v3
+        with:
+          name: constraints
+          path: ./files/constraints-*/constraints-*.txt
+          retention-days: 7
+
 
   static-checks:
     timeout-minutes: 45
@@ -999,9 +953,7 @@ jobs:
       JOB_ID: "helm-tests"
       USE_XDIST: "true"
     if: >
-      needs.build-info.outputs.needs-helm-tests == 'true' &&
-      (github.repository == 'apache/airflow' || github.event_name != 
'schedule') &&
-      needs.build-info.outputs.default-branch == 'main'
+      needs.build-info.outputs.needs-helm-tests == 'true' && 
needs.build-info.outputs.default-branch == 'main'
     steps:
       - name: Cleanup repo
         run: docker run -v "${GITHUB_WORKSPACE}:/workspace" -u 0:0 bash -c "rm 
-rf /workspace/*"
@@ -1644,10 +1596,93 @@ jobs:
           echo
           echo Total number of unique warnings $(cat 
./artifacts/test-warnings*/* | sort | uniq | wc -l)
 
+  build-prod-images:
+    timeout-minutes: 80
+    name: >
+      ${{needs.build-info.outputs.build-job-description}} PROD images
+      ${{needs.build-info.outputs.all-python-versions-list-as-string}}
+    runs-on: ${{fromJSON(needs.build-info.outputs.runs-on)}}
+    needs: [build-info, build-ci-images]
+    env:
+      DEFAULT_BRANCH: ${{ needs.build-info.outputs.default-branch }}
+      DEFAULT_CONSTRAINTS_BRANCH: ${{ 
needs.build-info.outputs.default-constraints-branch }}
+      RUNS_ON: "${{needs.build-info.outputs.runs-on}}"
+      BACKEND: sqlite
+      VERSION_SUFFIX_FOR_PYPI: "dev0"
+      DEBUG_RESOURCES: ${{needs.build-info.outputs.debug-resources}}
+      # Force more parallelism for build even on public images
+      PARALLELISM: 6
+    steps:
+      - name: Cleanup repo
+        run: docker run -v "${GITHUB_WORKSPACE}:/workspace" -u 0:0 bash -c "rm 
-rf /workspace/*"
+        if: needs.build-info.outputs.in-workflow-build == 'true'
+      - uses: actions/checkout@v4
+        with:
+          ref: ${{ needs.build-info.outputs.targetCommitSha }}
+          persist-credentials: false
+        if: needs.build-info.outputs.in-workflow-build == 'true'
+      - name: "Install Breeze"
+        uses: ./.github/actions/breeze
+        if: needs.build-info.outputs.in-workflow-build == 'true'
+      - name: >
+          Build PROD Images
+          
${{needs.build-info.outputs.all-python-versions-list-as-string}}:${{env.IMAGE_TAG}}
+        uses: ./.github/actions/build-prod-images
+        if: needs.build-info.outputs.in-workflow-build == 'true'
+        with:
+          build-provider-packages: ${{ needs.build-info.outputs.default-branch 
== 'main' }}
+        env:
+          UPGRADE_TO_NEWER_DEPENDENCIES: ${{ 
needs.build-info.outputs.upgrade-to-newer-dependencies }}
+          DOCKER_CACHE: ${{ needs.build-info.outputs.cache-directive }}
+          PYTHON_VERSIONS: 
${{needs.build-info.outputs.all-python-versions-list-as-string}}
+          DEBUG_RESOURCES: ${{ needs.build-info.outputs.debug-resources }}
+
+  build-prod-images-bullseye:
+    timeout-minutes: 80
+    name: >
+      ${{needs.build-info.outputs.build-job-description}} Bullseye PROD images
+      ${{needs.build-info.outputs.all-python-versions-list-as-string}}
+    runs-on: ${{fromJSON(needs.build-info.outputs.runs-on)}}
+    needs: [build-info, build-ci-images]
+    if: needs.build-info.outputs.canary-run == 'true'
+    env:
+      DEFAULT_BRANCH: ${{ needs.build-info.outputs.default-branch }}
+      DEFAULT_CONSTRAINTS_BRANCH: ${{ 
needs.build-info.outputs.default-constraints-branch }}
+      RUNS_ON: "${{needs.build-info.outputs.runs-on}}"
+      BACKEND: sqlite
+      VERSION_SUFFIX_FOR_PYPI: "dev0"
+      DEBUG_RESOURCES: ${{needs.build-info.outputs.debug-resources}}
+      # Force more parallelism for build even on public images
+      PARALLELISM: 6
+    steps:
+      - name: Cleanup repo
+        run: docker run -v "${GITHUB_WORKSPACE}:/workspace" -u 0:0 bash -c "rm 
-rf /workspace/*"
+      - uses: actions/checkout@v3
+        with:
+          ref: ${{ needs.build-info.outputs.targetCommitSha }}
+          persist-credentials: false
+          submodules: recursive
+      - name: "Install Breeze"
+        uses: ./.github/actions/breeze
+      - name: >
+          Build Bullseye PROD Images
+          
${{needs.build-info.outputs.all-python-versions-list-as-string}}:${{env.IMAGE_TAG}}
+        uses: ./.github/actions/build-prod-images
+        with:
+          build-provider-packages: ${{ needs.build-info.outputs.default-branch 
== 'main' }}
+        env:
+          UPGRADE_TO_NEWER_DEPENDENCIES: ${{ 
needs.build-info.outputs.upgrade-to-newer-dependencies }}
+          DOCKER_CACHE: ${{ needs.build-info.outputs.cache-directive }}
+          PYTHON_VERSIONS: 
${{needs.build-info.outputs.all-python-versions-list-as-string}}
+          DEBUG_RESOURCES: ${{ needs.build-info.outputs.debug-resources }}
+          DEBIAN_VERSION: "bullseye"
+          # Do not override the "bookworm" image - just push a new bullseye 
image
+          # TODO: improve caching for that build
+          IMAGE_TAG: "bullseye-${{ github.event.pull_request.head.sha || 
github.sha }}"
 
   wait-for-prod-images:
     timeout-minutes: 80
-    name: "Wait for & verify PROD images"
+    name: "Wait for PROD images"
     runs-on: ${{fromJSON(needs.build-info.outputs.runs-on)}}
     needs: [build-info, wait-for-ci-images, build-prod-images]
     if: needs.build-info.outputs.prod-image-build == 'true'
@@ -1660,12 +1695,15 @@ jobs:
     steps:
       - name: Cleanup repo
         run: docker run -v "${GITHUB_WORKSPACE}:/workspace" -u 0:0 bash -c "rm 
-rf /workspace/*"
+        if: needs.build-info.outputs.in-workflow-build == 'false'
       - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
         uses: actions/checkout@v4
         with:
           persist-credentials: false
+        if: needs.build-info.outputs.in-workflow-build == 'false'
       - name: "Install Breeze"
         uses: ./.github/actions/breeze
+        if: needs.build-info.outputs.in-workflow-build == 'false'
       - name: Wait for PROD images ${{ env.PYTHON_VERSIONS }}:${{ 
env.IMAGE_TAG }}
         # We wait for the images to be available either from 
"build-images.yml' run as pull_request_target
         # or from build-prod-images above.
@@ -1675,15 +1713,36 @@ jobs:
         env:
           PYTHON_VERSIONS: ${{ 
needs.build-info.outputs.python-versions-list-as-string }}
           DEBUG_RESOURCES: ${{needs.build-info.outputs.debug-resources}}
+        if: needs.build-info.outputs.in-workflow-build == 'false'
+
+  verify-prod-images:
+    timeout-minutes: 80
+    name: Verify PROD images 
${{needs.build-info.outputs.all-python-versions-list-as-string}}
+    runs-on: ${{fromJSON(needs.build-info.outputs.runs-on)}}
+    needs: [build-info, wait-for-prod-images]
+    env:
+      DEFAULT_BRANCH: ${{ needs.build-info.outputs.default-branch }}
+      DEFAULT_CONSTRAINTS_BRANCH: ${{ 
needs.build-info.outputs.default-constraints-branch }}
+      RUNS_ON: "${{needs.build-info.outputs.runs-on}}"
+      VERSION_SUFFIX_FOR_PYPI: "dev0"
+    steps:
+      - name: Cleanup repo
+        run: docker run -v "${GITHUB_WORKSPACE}:/workspace" -u 0:0 bash -c "rm 
-rf /workspace/*"
+      - uses: actions/checkout@v4
+        with:
+          ref: ${{ needs.build-info.outputs.targetCommitSha }}
+          persist-credentials: false
+      - name: Pull PROD images ${{ env.PYTHON_VERSIONS }}:${{ env.IMAGE_TAG }}
+        run: breeze prod-image pull --run-in-parallel --tag-as-latest
+        env:
+          # Force more parallelism for pull even on public images
+          PARALLELISM: 6
       - name: Verify PROD images ${{ env.PYTHON_VERSIONS }}:${{ env.IMAGE_TAG 
}}
-        # We pull images again (which is a NOOP) but this time we also verify 
the images
-        # Having it as a separate step allows us to see if the problem was 
with waiting or verification
-        run: breeze prod-image pull --verify --run-in-parallel
+        run: breeze prod-image verify --run-in-parallel
         env:
-          PYTHON_VERSIONS: ${{ 
needs.build-info.outputs.python-versions-list-as-string }}
+          PYTHON_VERSIONS: 
${{needs.build-info.outputs.all-python-versions-list-as-string}}
           DEBUG_RESOURCES: ${{needs.build-info.outputs.debug-resources}}
 
-
   test-docker-compose-quick-start:
     timeout-minutes: 60
     name: "Test docker-compose quick start"
@@ -1700,12 +1759,12 @@ jobs:
         with:
           fetch-depth: 2
           persist-credentials: false
-      - name: >
-          Prepare breeze & PROD image:
-          
${{needs.build-info.outputs.default-python-version}}:${{env.IMAGE_TAG}}
-        uses: ./.github/actions/prepare_breeze_and_image
-        with:
-          pull-image-type: 'PROD'
+      - name: Pull PROD images ${{ env.PYTHON_VERSIONS }}:${{ env.IMAGE_TAG }}
+        run: breeze prod-image pull --run-in-parallel --tag-as-latest
+        env:
+          PYTHON_VERSIONS: ${{ 
needs.build-info.outputs.python-versions-list-as-string }}
+          # Force more parallelism for pull even on public images
+          PARALLELISM: 6
       - name: "Test docker-compose quick start"
         run: breeze testing docker-compose-tests
 
@@ -1741,6 +1800,8 @@ jobs:
         run: breeze prod-image pull --run-in-parallel --tag-as-latest
         env:
           PYTHON_VERSIONS: ${{ 
needs.build-info.outputs.python-versions-list-as-string }}
+          # Force more parallelism for pull even on public images
+          PARALLELISM: 6
       - name: "Cache bin folder with tools for kubernetes testing"
         uses: actions/cache@v3
         with:
@@ -1796,8 +1857,7 @@ jobs:
       - tests-no-db
       - tests-integration-postgres
       - tests-integration-mysql
-      # Skip when generate constraints fails
-      - generate-constraints
+      - preview-constraints
     env:
       RUNS_ON: "${{needs.build-info.outputs.runs-on}}"
       DEBUG_RESOURCES: ${{needs.build-info.outputs.debug-resources}}
diff --git a/CI.rst b/CI.rst
index 867686d8e7..5c2e00e80a 100644
--- a/CI.rst
+++ b/CI.rst
@@ -239,49 +239,39 @@ Regular PR builds run in a "stable" environment:
 * no ARM image builds are build in the regular PRs
 * lower probability of flaky tests for non-committer PRs (public runners and 
less parallelism)
 
+Maintainers can also run the "Pull Request run" from the "apache/airflow" 
repository by pushing
+to a branch in the "apache/airflow" repository. This is useful when you want 
to test a PR that
+changes the CI/CD infrastructure itself (for example changes to the CI/CD 
scripts or changes to
+the CI/CD workflows). In this case the PR is run in the context of the 
"apache/airflow" repository
+and has WRITE access to the GitHub Container Registry.
+
 Canary run
 ----------
 
-Those runs are results of direct pushes done by the committers - basically 
merging of a Pull Request
-by the committers. Those runs execute in the context of the Apache Airflow 
Code Repository and have also
-write permission for GitHub resources (container registry, code repository).
-
-The main purpose for the run is to check if the code after merge still holds 
all the assertions - like
-whether it still builds, all tests are green. This is a "Canary" build that 
helps us to detect early
-problems with dependencies, image building, full matrix of tests in case they 
passed through selective checks.
-
-This is needed because some of the conflicting changes from multiple PRs might 
cause build and test failures
-after merge even if they do not fail in isolation. Also those runs are already 
reviewed and confirmed by the
-committers so they can be used to do some housekeeping:
+This is the flow that happens when a pull request is merged to the "main" 
branch or pushed to any of
+the "v2-*-test" branches. The "Canary" run attempts to upgrade dependencies to 
the latest versions
+and quickly pushes a preview of cache the CI/PROD images to the GitHub 
Registry - so that pull requests
+can quickly use the new cache - this is useful when Dockerfile or installation 
scripts change because such
+cache will already have the latest Dockerfile and scripts pushed even if some 
tests will fail.
+When successful, the run updates the constraints files in the 
"constraints-main" branch with the latest
+constraints and pushes both cache and latest  CI/PROD images to the GitHub 
Registry.
 
-- pushing most recent image build in the PR to the GitHub Container Registry 
(for caching) including recent
-  Dockerfile changes and setup.py/setup.cfg changes (Early Cache)
-- test that image in ``breeze`` command builds quickly
-- run full matrix of tests to detect any tests that will be mistakenly missed 
in ``selective checks``
-- upgrading to latest constraints and pushing those constraints if all tests 
succeed
-- refresh latest Python base images in case new patch-level is released
+When "Canary" build fails, it's often a sign that some of our dependencies 
released a new version that
+is not compatible with current tests or Airflow code, Also it might mean that 
a breaking change has been
+merged to "main". Both cases should be addressed quickly by the maintainers. 
The "broken main" by our code
+should be fixed quickly, while the "broken dependencies" can take a bit of 
time to fix as until the tests
+succeeds, constraints will not be updated, which means that regular PRs will 
continue using the old version
+of dependencies that already passed one of the previous "Canary" runs.
 
-The housekeeping is important - Python base images are refreshed with varying 
frequency (once every few months
-usually but sometimes several times per week) with the latest security and bug 
fixes.
 
 Scheduled runs
 --------------
 
-Those runs are results of (nightly) triggered job - only for ``main`` branch. 
The
-main purpose of the job is to check if there was no impact of external 
dependency changes on the Apache
-Airflow code (for example transitive dependencies released that fail the 
build). It also checks if the
-Docker images can be built from the scratch (again - to see if some 
dependencies have not changed - for
-example downloaded package releases etc.
-
-All runs consist of the same jobs, but the jobs behave slightly differently or 
they are skipped in different
-run categories. Here is a summary of the run categories with regards of the 
jobs they are running.
-Those jobs often have matrix run strategy which runs several different 
variations of the jobs
-(with different Backend type / Python version, type of the tests to run for 
example). The following chapter
-describes the workflows that execute for each run.
-
-Those runs and their corresponding ``Build Images`` runs are only executed in 
main ``apache/airflow``
-repository, they are not executed in forks - we want to be nice to the 
contributors and not use their
-free build minutes on GitHub Actions.
+This is the flow that happens when a scheduled run is triggered. The 
"scheduled" workflow is aimed to
+run regularly (overnight). Scheduled run is generally the same as "Canary" 
run, with the difference
+that the image is build always from the scratch and not from the cache. This 
way we can check that no
+"system" dependencies in debian base image have changed and that the build is 
still reproducible.
+No separate diagram is needed for scheduled run as it is identical to that of 
"Canary" run.
 
 Workflows
 =========
@@ -364,7 +354,7 @@ This workflow is a regular workflow that performs all 
checks of Airflow code.
 
+---------------------------------+----------------------------------------------------------+----------+----------+-----------+-------------------+
 | Build CI images                 | Builds images in-workflow (not in the 
``build images``)  | -        | Yes      | Yes (1)   | Yes (4)           |
 
+---------------------------------+----------------------------------------------------------+----------+----------+-----------+-------------------+
-| Generate constraints            | Generates constraints that were updated in 
this build    | Yes (2)  | Yes (2)  | Yes (2)   | Yes (2)           |
+| Preview constraints             | Preview constraints that were updated in 
this build      | Yes (2)  | Yes (2)  | Yes (2)   | Yes (2)           |
 
+---------------------------------+----------------------------------------------------------+----------+----------+-----------+-------------------+
 | Build PROD images               | Builds images in-workflow (not in the 
``build images``)  | -        | Yes      | Yes (1)   | Yes (4)           |
 
+---------------------------------+----------------------------------------------------------+----------+----------+-----------+-------------------+
diff --git a/CI_DIAGRAMS.md b/CI_DIAGRAMS.md
index 8bc1baabaf..9b349140da 100644
--- a/CI_DIAGRAMS.md
+++ b/CI_DIAGRAMS.md
@@ -23,11 +23,21 @@ You can see here the sequence diagrams of the flow 
happening during the CI Jobs.
 
 ## Pull request flow from fork
 
+This is the flow that happens when a pull request is created from a fork - 
which is the most frequent
+pull request flow that happens in Airflow. The "pull_request" workflow does 
not have write access
+to the GitHub Registry, so it cannot push the CI/PROD images there. Instead, 
we push the images
+from the "pull_request_target" workflow, which has write access to the GitHub 
Registry. Note that
+this workflow always uses scripts and workflows from the "target" branch of 
the "apache/airflow"
+repository, so the user submitting such pull request cannot override our build 
scripts and inject malicious
+code into the workflow that has potentially write access to the GitHub 
Registry (and can override cache).
+
+Security is the main reason why we have two workflows for pull requests and 
such complex workflows.
+
 ```mermaid
 sequenceDiagram
     Note over Airflow Repo: pull request
-    Note over Tests: pull_request<br>[Read Token]
-    Note over Build Images: pull_request_target<br>[Write Token]
+    Note over Tests: pull_request<br>[Write Token]
+    Note over Build Images: pull_request_target<br>[Unused Token]
     activate Airflow Repo
     Airflow Repo -->> Tests: Trigger 'pull_request'
     activate Tests
@@ -39,6 +49,8 @@ sequenceDiagram
     Note over Tests: Skip Build<br>(Runs in 'Build Images')<br>PROD Images
     par
         Note over Build Images: Build CI Images<br>[COMMIT_SHA]<br>Use latest 
constraints<br>or upgrade if setup changed
+        Build Images ->> GitHub Registry: Push CI Images<br>[COMMIT_SHA]
+        Build Images ->> Artifacts: Upload source constraints
     and
         Note over Tests: OpenAPI client gen
     and
@@ -52,14 +64,18 @@ sequenceDiagram
             Note over Tests: Run basic <br>static checks
         end
     end
-    Build Images ->> GitHub Registry: Push CI Images<br>[COMMIT_SHA]
     loop Wait for CI images
         GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
     end
-    Note over Tests: Verify CI Images<br>[COMMIT_SHA]
     par
-        GitHub Registry ->> Build Images: Pull PROD Images<br>[latest]
+        GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
+        Note over Tests: Verify CI Images<br>[COMMIT_SHA]
+        Note over Tests: Generate/Preview 
constraints<br>source,pypi,no-providers
+        Tests ->> Artifacts: Upload source,pypi,no-providers constraints
+    and
+        Artifacts ->> Tests: Download source constraints
         Note over Build Images: Build PROD Images<br>[COMMIT_SHA]
+        Build Images ->> GitHub Registry: Push PROD Images<br>[COMMIT_SHA]
     and
         opt
             GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
@@ -68,53 +84,78 @@ sequenceDiagram
     and
         opt
             GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-            Note over Tests: Build docs / Spellcheck docs
+            Note over Tests: Build docs
+        end
+    and
+        opt
+            GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
+            Note over Tests: Spellcheck docs
         end
     and
         opt
             GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
             Note over Tests: Test Pytest collection<br>[COMMIT_SHA]
-            par
-                opt
-                    GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-                    Note over Tests: Unit Tests<br>Python/DB matrix
-                end
-            and
-                opt
-                     GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-                     Note over Tests: Integration Tests
-                end
-            and
-                opt
-                     GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-                     Note over Tests: Quarantined Tests
-                end
-            end
         end
     and
         opt
-             GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-             Note over Tests: Test provider <br>packages build
+            GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
+            Note over Tests: Unit Tests<br>Python/DB matrix
+        end
+    and
+        opt
+            GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
+            Note over Tests: Unit Tests<br>Python/Non-DB matrix
+        end
+    and
+        opt
+            GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
+            Note over Tests: Integration Tests
+        end
+    and
+        opt
+            GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
+            Note over Tests: Quarantined Tests
+        end
+    and
+        opt
+            GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
+            Note over Tests: Test provider <br>packages build<br>wheel, sdist, 
old airflow
         end
     and
         opt
-             GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-             Note over Tests: Test airflow <br>packages build
+            GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
+            Note over Tests: Test airflow <br>release commands
         end
     and
         opt
-             GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-             Note over Tests: Helm tests
+            GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
+            Note over Tests: Helm tests
         end
     end
-    Note over Tests: Summarize Warnings
-    Build Images ->> GitHub Registry: Push PROD Images<br>[COMMIT_SHA]
-    deactivate Build Images
-    loop Wait for PROD images
-        GitHub Registry ->> Tests: Pull PROD Images<br>[COMMIT_SHA]
+    par
+        Note over Tests: Summarize Warnings
+    and
+        opt
+            Artifacts ->> Tests: Download source,pypi,no-providers constraints
+            Note over Tests: Display constraints diff
+        end
+    and
+        opt
+            loop Wait for PROD images
+                GitHub Registry ->> Tests: Pull PROD Images<br>[COMMIT_SHA]
+            end
+        end
+    and
+        opt
+            Note over Tests: Build ARM CI images
+        end
     end
-    Note over Tests: Verify PROD Image<br>[COMMIT_SHA]
     par
+        opt
+            GitHub Registry ->> Tests: Pull PROD Images<br>[COMMIT_SHA]
+            Note over Tests: Verify PROD Images<br>[COMMIT_SHA]
+        end
+    and
         opt
             GitHub Registry ->> Tests: Pull PROD Images<br>[COMMIT_SHA]
             Note over Tests: Run Kubernetes <br>tests
@@ -125,11 +166,6 @@ sequenceDiagram
             Note over Tests: Run docker-compose <br>tests
         end
     end
-    opt
-        GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-        Note over Tests: Generate constraints
-    end
-    Note over Tests: Build ARM CI images
     Tests -->> Airflow Repo: Status update
     deactivate Airflow Repo
     deactivate Tests
@@ -137,6 +173,16 @@ sequenceDiagram
 
 ## Pull request flow from "apache/airflow" repo
 
+The difference between this flow and the previous one is that the CI/PROD 
images are built in the
+CI workflow and pushed to the GitHub Registry from there. This cannot be done 
in case of fork
+pull request, because Pull Request from forks cannot have "write" access to 
GitHub Registry. All the steps
+except "Build Info" from the "Build Images" workflows are skipped in this case.
+
+THis workflow can be used by maintainers in case they have a Pull Request that 
changes the scripts and
+CI workflows used to build images, because in this case the "Build Images" 
workflow will use them
+from the Pull Request. This is safe, because the Pull Request is from the 
"apache/airflow" repository
+and only maintainers can push to that repository and create Pull Requests from 
it.
+
 ```mermaid
 sequenceDiagram
     Note over Airflow Repo: pull request
@@ -154,10 +200,12 @@ sequenceDiagram
     Note over Tests: Build info<br>Decide on tests<br>Decide on Matrix 
(selective)
     par
         Note over Tests: Build CI Images<br>[COMMIT_SHA]<br>Use latest 
constraints<br>or upgrade if setup changed
+        Tests ->> GitHub Registry: Push CI Images<br>[COMMIT_SHA]
+        Tests ->> Artifacts: Upload source constraints
     and
         Note over Tests: OpenAPI client gen
     and
-        Note over Tests: Test UI
+        Note over Tests: React WWW tests
     and
         Note over Tests: Test examples<br>PROD image building
     and
@@ -167,11 +215,16 @@ sequenceDiagram
             Note over Tests: Run basic <br>static checks
         end
     end
-    Tests ->> GitHub Registry: Push CI Images<br>[COMMIT_SHA]
-    GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-    Note over Tests: Verify CI Image<br>[COMMIT_SHA]
+    Note over Tests: Skip waiting for CI images
     par
+        GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
+        Note over Tests: Verify CI Images<br>[COMMIT_SHA]
+        Note over Tests: Generate/Preview 
constraints<br>source,pypi,no-providers
+        Tests ->> Artifacts: Upload source,pypi,no-providers constraints
+    and
+        Artifacts ->> Tests: Download source constraints
         Note over Tests: Build PROD Images<br>[COMMIT_SHA]
+        Tests ->> GitHub Registry: Push PROD Images<br>[COMMIT_SHA]
     and
         opt
             GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
@@ -180,50 +233,70 @@ sequenceDiagram
     and
         opt
             GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-            Note over Tests: Build docs/ Spellcheck docs
+            Note over Tests: Build docs
+        end
+    and
+        opt
+            GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
+            Note over Tests: Spellcheck docs
         end
     and
         opt
             GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
             Note over Tests: Test Pytest collection<br>[COMMIT_SHA]
-            par
-                opt
-                    GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-                    Note over Tests: Unit Tests<br>Python/DB matrix/No DB
-                end
-            and
-                opt
-                     GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-                     Note over Tests: Integration Tests
-                end
-            and
-                opt
-                     GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-                     Note over Tests: Quarantined Tests
-                end
-            end
         end
     and
         opt
-             GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-             Note over Tests: Test provider <br>packages build
+            GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
+            Note over Tests: Unit Tests<br>Python/DB matrix
+        end
+    and
+        opt
+            GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
+            Note over Tests: Unit Tests<br>Python/Non-DB matrix
+        end
+    and
+        opt
+            GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
+            Note over Tests: Integration Tests
+        end
+    and
+        opt
+            GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
+            Note over Tests: Quarantined Tests
+        end
+    and
+        opt
+            GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
+            Note over Tests: Test provider <br>packages build<br>wheel, sdist, 
old airflow
         end
     and
         opt
-             GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-             Note over Tests: Test airflow <br>packages build
+            GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
+            Note over Tests: Test airflow <br>release commands
         end
     and
         opt
-             GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-             Note over Tests: Helm tests
+            GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
+            Note over Tests: Helm tests
         end
     end
-    Note over Tests: Summarize Warnings
-    Tests ->> GitHub Registry: Push PROD Images<br>[COMMIT_SHA]
-    GitHub Registry ->> Tests: Pull PROD Image<br>[COMIT_SHA]
-    Note over Tests: Verify PROD Image<br>[COMMIT_SHA]
+    Note over Tests: Skip waiting for PROD images
     par
+        Note over Tests: Summarize Warnings
+    and
+        opt
+            Artifacts ->> Tests: Download source,pypi,no-providers constraints
+            Note over Tests: Display constraints diff
+        end
+    and
+        Note over Tests: Build ARM CI images
+    and
+        opt
+            GitHub Registry ->> Tests: Pull PROD Images<br>[COMMIT_SHA]
+            Note over Tests: Verify PROD Images<br>[COMMIT_SHA]
+        end
+    and
         opt
             GitHub Registry ->> Tests: Pull PROD Images<br>[COMMIT_SHA]
             Note over Tests: Run Kubernetes <br>tests
@@ -234,11 +307,6 @@ sequenceDiagram
             Note over Tests: Run docker-compose <br>tests
         end
     end
-    opt
-        GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-        Note over Tests: Generate constraints
-    end
-    Note over Tests: Build ARM CI images
     Tests -->> Airflow Repo: Status update
     deactivate Airflow Repo
     deactivate Tests
@@ -246,6 +314,14 @@ sequenceDiagram
 
 ## Merge "Canary" run
 
+This is the flow that happens when a pull request is merged to the "main" 
branch or pushed to any of
+the "v2-*-test" branches. The "Canary" run attempts to upgrade dependencies to 
the latest versions
+and quickly pushes a preview of cache the CI/PROD images to the GitHub 
Registry - so that pull requests
+can quickly use the new cache - this is useful when Dockerfile or installation 
scripts change because such
+cache will already have the latest Dockerfile and scripts pushed even if some 
tests will fail.
+When successful, the run updates the constraints files in the 
"constraints-main" branch with the latest
+constraints and pushes both cache and latest  CI/PROD images to the GitHub 
Registry.
+
 ```mermaid
 sequenceDiagram
     Note over Airflow Repo: push/merge
@@ -256,6 +332,8 @@ sequenceDiagram
     Note over Tests: Build info<br>All tests<br>Full matrix
     par
         Note over Tests: Build CI Images<br>[COMMIT_SHA]<br>Always upgrade deps
+        Tests ->> GitHub Registry: Push CI Images<br>[COMMIT_SHA]
+        Tests ->> Artifacts: Upload source constraints
     and
         Note over Tests: Check that image builds quickly
     and
@@ -269,147 +347,88 @@ sequenceDiagram
     and
         Note over Tests: Test git clone on Windows
     end
-    Tests ->> GitHub Registry: Push CI Images<br>[COMMIT_SHA]
-    GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-    Note over Tests: Verify CI Image<br>[COMMIT_SHA]
+    Note over Tests: Skip waiting for CI images
     par
+        GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
+        Note over Tests: Verify CI Images<br>[COMMIT_SHA]
+        Note over Tests: Generate/Preview 
constraints<br>source,pypi,no-providers
+        Tests ->> Artifacts: Upload source,pypi,no-providers constraints
+    and
+        Artifacts ->> Tests: Download source constraints
         Note over Tests: Build PROD Images<br>[COMMIT_SHA]
+        Tests ->> GitHub Registry: Push PROD Images<br>[COMMIT_SHA]
     and
         GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
         Note over Tests: Run static checks
     and
         GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-        Note over Tests: Build docs / Spellcheck docs
+        Note over Tests: Build docs
     and
         GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-        Note over Tests: Test Pytest collection<br>[COMMIT_SHA]
-        par
-            GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-            Note over Tests: Unit Tests<br>Python/DB matrix
-        and
-            GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-            Note over Tests: Integration Tests
-        and
-           GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-           Note over Tests: Quarantined Tests
-        end
-    and
-        opt
-           GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-           Note over Tests: Test provider <br>packages build
-        end
+        Note over Tests: Spellcheck docs
     and
         GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-        Note over Tests: Test airflow <br>packages build
-    and
-        opt
-             GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-             Note over Tests: Helm tests
-        end
-    end
-    Note over Tests: Summarize Warnings
-    Tests ->> GitHub Registry: Push PROD Images<br>[COMMIT_SHA]
-    GitHub Registry ->> Tests: Pull PROD Image<br>[COMMIT_SHA]
-    Note over Tests: Verify PROD Image<br>[COMMIT_SHA]
-    par
-        GitHub Registry ->> Tests: Pull PROD Image<br>[COMMIT_SHA]
-        Note over Tests: Run Kubernetes <br>tests
-    and
-        GitHub Registry ->> Tests: Pull PROD Image<br>[COMMIT_SHA]
-        Note over Tests: Run docker-compose <br>tests
-    end
-    GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-    Note over Tests: Generate constraints
-    Tests ->> Airflow Repo: Push constraints if changed
-    Note over Tests: Build CI Images<br>[latest]<br>Use latest constraints
-    Tests ->> GitHub Registry: Push CI Image<br>[latest]
-    Note over Tests: Build PROD Images<br>[latest]<br>Use latest constraints
-    Tests ->> GitHub Registry: Push PROD Image<br>[latest]
-    Note over Tests: Build ARM CI images
-    Tests ->> GitHub Registry: Push ARM CI Image cache
-    Tests -->> Airflow Repo: Status update
-    deactivate Airflow Repo
-    deactivate Tests
-```
-
-## Scheduled run
-
-```mermaid
-sequenceDiagram
-    Note over Airflow Repo: scheduled
-    Note over Tests: push<br>[Write Token]
-    activate Airflow Repo
-    Airflow Repo -->> Tests: Trigger 'schedule'
-    activate Tests
-    Note over Tests: Build info<br>All tests<br>Full matrix
-    par
-        GitHub Registry ->> Tests: Pull CI Images<br>[latest]
-        Note over Tests: Build CI Images<br>[COMMIT_SHA]<br>Always upgrade deps
-    and
-        Note over Tests: OpenAPI client gen
-    and
-        Note over Tests: React WWW tests
-    and
-        Note over Tests: Test examples<br>PROD image building
+        Note over Tests: Test Pytest collection<br>[COMMIT_SHA]
     and
-        Note over Tests: Build CI Images<br>Use original constraints
-    end
-    Tests ->> GitHub Registry: Push CI Images<br>[COMMIT_SHA]
-    GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-    Note over Tests: Verify CI Image<br>[COMMIT_SHA]
-    par
-        GitHub Registry ->> Tests: Pull PROD Images<br>[latest]
-        Note over Tests: Build PROD Images<br>[COMMIT_SHA]
+        GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
+        Note over Tests: Unit Tests<br>Python/DB matrix
     and
         GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-        Note over Tests: Run static checks
+        Note over Tests: Unit Tests<br>Python/Non-DB matrix
     and
         GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-        Note over Tests: Build docs / Spellcheck docs
+        Note over Tests: Integration Tests
     and
         GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-        Note over Tests: Test Pytest collection<br>[COMMIT_SHA]
-        par
-            GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-            Note over Tests: Unit Tests<br>Python/DB matrix
-        and
-            GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-            Note over Tests: Integration Tests
-        and
-           GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-           Note over Tests: Quarantined Tests
-        end
+        Note over Tests: Quarantined Tests
     and
         GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-        Note over Tests: Test provider <br>packages build
+        Note over Tests: Test provider <br>packages build<br>wheel, sdist, old 
airflow
     and
         GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-        Note over Tests: Test airflow <br>packages build
+        Note over Tests: Test airflow <br>release commands
     and
         GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
         Note over Tests: Helm tests
     end
-    Note over Tests: Summarize Warnings
-    Tests ->> GitHub Registry: Push PROD Images<br>[COMMIT_SHA]
-    GitHub Registry ->> Tests: Pull PROD Image<br>[COMMIT_SHA]
-    Note over Tests: Verify PROD Image<br>[COMMIT_SHA]
+    Note over Tests: Skip waiting for PROD images
     par
+        Note over Tests: Summarize Warnings
+    and
+        Artifacts ->> Tests: Download source,pypi,no-providers constraints
+        Note over Tests: Display constraints diff
+        Tests ->> Airflow Repo: Push constraints if changed to 
'constraints-BRANCH'
+    and
+        GitHub Registry ->> Tests: Pull PROD Images<br>[COMMIT_SHA]
+        Note over Tests: Verify PROD Images<br>[COMMIT_SHA]
+    and
         GitHub Registry ->> Tests: Pull PROD Image<br>[COMMIT_SHA]
         Note over Tests: Run Kubernetes <br>tests
     and
         GitHub Registry ->> Tests: Pull PROD Image<br>[COMMIT_SHA]
         Note over Tests: Run docker-compose <br>tests
     end
-    GitHub Registry ->> Tests: Pull CI Images<br>[COMMIT_SHA]
-    Note over Tests: Generate constraints
-    Tests ->> Airflow Repo: Push constraints if changed
-    Note over Tests: Build CI Images<br>[latest]<br>Use latest constraints
-    Tests ->> GitHub Registry: Push CI Image cache + latest
-    Note over Tests: Build PROD Images<br>[latest]<br>Use latest constraints
-    Tests ->> GitHub Registry: Push PROD Image cache + latest
-    Note over Tests: Build ARM CI images
-    Tests ->> GitHub Registry: Push ARM CI Image cache
+    par
+        Note over Tests: Build CI Images/cache<br>[latest]<br>Use pushed 
constraints
+        Tests ->> GitHub Registry: Push CI Images/cache[latest]
+        Note over Tests: Build PROD Images/cache<br>[latest]<br>Use pushed 
constraints
+        Tests ->> GitHub Registry: Push PROD Images/cache[latest]
+    and
+        Note over Tests: Build ARM CI images[latest]<br>Use pushed constraints
+        Tests ->> GitHub Registry: Push ARM CI Images[latest]
+        Note over Tests: Build ARM PROD images[latest]<br>Use pushed 
constraints
+        Tests ->> GitHub Registry: Push ARM PROD Images[latest]
+    end
     Tests -->> Airflow Repo: Status update
     deactivate Airflow Repo
     deactivate Tests
 ```
+
+## Scheduled run
+
+This is the flow that happens when a scheduled run is triggered. The 
"scheduled" workflow is aimed to
+run regularly (overnight) even if no new PRs are merged to "main". Scheduled 
run is generally the
+same as "Canary" run, with the difference that the image used to run the tests 
is built without using
+cache - it's always built from the scratch. This way we can check that no 
"system" dependencies in debian
+base image have changed and that the build is still reproducible. No separate 
diagram is needed for
+scheduled run as it is identical to that of "Canary" run.
diff --git a/dev/breeze/src/airflow_breeze/commands/ci_image_commands.py 
b/dev/breeze/src/airflow_breeze/commands/ci_image_commands.py
index f33fdad2ef..08722caf30 100644
--- a/dev/breeze/src/airflow_breeze/commands/ci_image_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/ci_image_commands.py
@@ -93,7 +93,12 @@ from airflow_breeze.utils.docker_command_utils import (
 from airflow_breeze.utils.image import run_pull_image, run_pull_in_parallel, 
tag_image_as_latest
 from airflow_breeze.utils.mark_image_as_refreshed import 
mark_image_as_refreshed
 from airflow_breeze.utils.md5_build_check import 
md5sum_check_if_build_is_needed
-from airflow_breeze.utils.parallel import DockerBuildxProgressMatcher, 
check_async_run_results, run_with_pool
+from airflow_breeze.utils.parallel import (
+    DockerBuildxProgressMatcher,
+    ShowLastLineProgressMatcher,
+    check_async_run_results,
+    run_with_pool,
+)
 from airflow_breeze.utils.path_utils import AIRFLOW_SOURCES_ROOT, 
BUILD_CACHE_DIR
 from airflow_breeze.utils.python_versions import get_python_version_list
 from airflow_breeze.utils.registry import login_to_github_docker_registry
@@ -476,6 +481,45 @@ def pull(
             sys.exit(return_code)
 
 
+def run_verify_in_parallel(
+    image_params_list: list[BuildCiParams],
+    python_version_list: list[str],
+    extra_pytest_args: tuple[str, ...],
+    include_success_outputs: bool,
+    parallelism: int,
+    skip_cleanup: bool,
+    debug_resources: bool,
+) -> None:
+    with ci_group(f"Verifying CI images for {python_version_list}"):
+        all_params = [f"CI {image_params.python}" for image_params in 
image_params_list]
+        with run_with_pool(
+            parallelism=parallelism,
+            all_params=all_params,
+            debug_resources=debug_resources,
+            progress_matcher=ShowLastLineProgressMatcher(),
+        ) as (pool, outputs):
+            results = [
+                pool.apply_async(
+                    verify_an_image,
+                    kwds={
+                        "image_name": image_params.airflow_image_name_with_tag,
+                        "image_type": "CI",
+                        "slim_image": False,
+                        "extra_pytest_args": extra_pytest_args,
+                        "output": outputs[index],
+                    },
+                )
+                for index, image_params in enumerate(image_params_list)
+            ]
+    check_async_run_results(
+        results=results,
+        success="All images verified",
+        outputs=outputs,
+        include_success_outputs=include_success_outputs,
+        skip_cleanup=skip_cleanup,
+    )
+
+
 @ci_image.command(
     name="verify",
     context_settings=dict(
@@ -484,22 +528,34 @@ def pull(
     ),
 )
 @option_python
+@option_python_versions
 @option_github_repository
 @option_image_tag_for_verifying
 @option_image_name
 @option_pull
 @option_github_token
+@option_run_in_parallel
+@option_parallelism
+@option_skip_cleanup
+@option_include_success_outputs
+@option_debug_resources
 @option_verbose
 @option_dry_run
 @click.argument("extra_pytest_args", nargs=-1, type=click.UNPROCESSED)
 def verify(
     python: str,
+    python_versions: str,
     image_name: str,
     image_tag: str | None,
     pull: bool,
     github_token: str,
     github_repository: str,
-    extra_pytest_args: tuple,
+    extra_pytest_args: tuple[str, ...],
+    run_in_parallel: bool,
+    parallelism: int,
+    skip_cleanup: bool,
+    debug_resources: bool,
+    include_success_outputs: bool,
 ):
     """Verify CI image."""
     perform_environment_checks()
@@ -507,27 +563,54 @@ def verify(
         github_token=github_token,
         output=None,
     )
-    if image_name is None:
-        build_params = BuildCiParams(
+    if (pull or image_name) and run_in_parallel:
+        get_console().print(
+            "[error]You cannot use --pull,--image-name and --run-in-parallel 
at the same time. " "Exiting[/]"
+        )
+        sys.exit(1)
+    if run_in_parallel:
+        base_build_params = BuildCiParams(
             python=python,
-            image_tag=image_tag,
             github_repository=github_repository,
-            github_token=github_token,
+            image_tag=image_tag,
         )
-        image_name = build_params.airflow_image_name_with_tag
-    if pull:
-        check_remote_ghcr_io_commands()
-        command_to_run = ["docker", "pull", image_name]
-        run_command(command_to_run, check=True)
-    get_console().print(f"[info]Verifying CI image: {image_name}[/]")
-    return_code, info = verify_an_image(
-        image_name=image_name,
-        output=None,
-        image_type="CI",
-        slim_image=False,
-        extra_pytest_args=extra_pytest_args,
-    )
-    sys.exit(return_code)
+        python_version_list = get_python_version_list(python_versions)
+        params_list: list[BuildCiParams] = []
+        for python in python_version_list:
+            build_params = deepcopy(base_build_params)
+            build_params.python = python
+            params_list.append(build_params)
+        run_verify_in_parallel(
+            image_params_list=params_list,
+            python_version_list=python_version_list,
+            extra_pytest_args=extra_pytest_args,
+            include_success_outputs=include_success_outputs,
+            parallelism=parallelism,
+            skip_cleanup=skip_cleanup,
+            debug_resources=debug_resources,
+        )
+    else:
+        if image_name is None:
+            build_params = BuildCiParams(
+                python=python,
+                image_tag=image_tag,
+                github_repository=github_repository,
+                github_token=github_token,
+            )
+            image_name = build_params.airflow_image_name_with_tag
+        if pull:
+            check_remote_ghcr_io_commands()
+            command_to_run = ["docker", "pull", image_name]
+            run_command(command_to_run, check=True)
+        get_console().print(f"[info]Verifying CI image: {image_name}[/]")
+        return_code, info = verify_an_image(
+            image_name=image_name,
+            output=None,
+            image_type="CI",
+            slim_image=False,
+            extra_pytest_args=extra_pytest_args,
+        )
+        sys.exit(return_code)
 
 
 def should_we_run_the_build(build_ci_params: BuildCiParams) -> bool:
diff --git a/dev/breeze/src/airflow_breeze/commands/ci_image_commands_config.py 
b/dev/breeze/src/airflow_breeze/commands/ci_image_commands_config.py
index e128aa035b..021bec3a68 100644
--- a/dev/breeze/src/airflow_breeze/commands/ci_image_commands_config.py
+++ b/dev/breeze/src/airflow_breeze/commands/ci_image_commands_config.py
@@ -144,6 +144,17 @@ CI_IMAGE_TOOLS_PARAMETERS: dict[str, list[dict[str, str | 
list[str]]]] = {
                 "--pull",
             ],
         },
+        {
+            "name": "Parallel running",
+            "options": [
+                "--run-in-parallel",
+                "--parallelism",
+                "--python-versions",
+                "--skip-cleanup",
+                "--debug-resources",
+                "--include-success-outputs",
+            ],
+        },
         {
             "name": "Github authentication",
             "options": [
diff --git 
a/dev/breeze/src/airflow_breeze/commands/production_image_commands.py 
b/dev/breeze/src/airflow_breeze/commands/production_image_commands.py
index ca1780738d..27f195bc2e 100644
--- a/dev/breeze/src/airflow_breeze/commands/production_image_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/production_image_commands.py
@@ -86,7 +86,12 @@ from airflow_breeze.utils.docker_command_utils import (
     warm_up_docker_builder,
 )
 from airflow_breeze.utils.image import run_pull_image, run_pull_in_parallel, 
tag_image_as_latest
-from airflow_breeze.utils.parallel import DockerBuildxProgressMatcher, 
check_async_run_results, run_with_pool
+from airflow_breeze.utils.parallel import (
+    DockerBuildxProgressMatcher,
+    ShowLastLineProgressMatcher,
+    check_async_run_results,
+    run_with_pool,
+)
 from airflow_breeze.utils.path_utils import AIRFLOW_SOURCES_ROOT, 
DOCKER_CONTEXT_DIR
 from airflow_breeze.utils.python_versions import get_python_version_list
 from airflow_breeze.utils.registry import login_to_github_docker_registry
@@ -448,6 +453,45 @@ def pull_prod_image(
             sys.exit(return_code)
 
 
+def run_verify_in_parallel(
+    image_params_list: list[BuildProdParams],
+    python_version_list: list[str],
+    extra_pytest_args: tuple[str, ...],
+    include_success_outputs: bool,
+    parallelism: int,
+    skip_cleanup: bool,
+    debug_resources: bool,
+) -> None:
+    with ci_group(f"Verifying PROD images for {python_version_list}"):
+        all_params = [f"PROD {image_params.python}" for image_params in 
image_params_list]
+        with run_with_pool(
+            parallelism=parallelism,
+            all_params=all_params,
+            debug_resources=debug_resources,
+            progress_matcher=ShowLastLineProgressMatcher(),
+        ) as (pool, outputs):
+            results = [
+                pool.apply_async(
+                    verify_an_image,
+                    kwds={
+                        "image_name": image_params.airflow_image_name_with_tag,
+                        "image_type": "PROD",
+                        "slim_image": False,
+                        "extra_pytest_args": extra_pytest_args,
+                        "output": outputs[index],
+                    },
+                )
+                for index, image_params in enumerate(image_params_list)
+            ]
+    check_async_run_results(
+        results=results,
+        success="All images verified",
+        outputs=outputs,
+        include_success_outputs=include_success_outputs,
+        skip_cleanup=skip_cleanup,
+    )
+
+
 @prod_image.command(
     name="verify",
     context_settings=dict(
@@ -455,22 +499,29 @@ def pull_prod_image(
         allow_extra_args=True,
     ),
 )
-@option_python
-@option_image_tag_for_verifying
-@option_image_name
-@option_pull
 @click.option(
     "--slim-image",
     help="The image to verify is slim and non-slim tests should be skipped.",
     is_flag=True,
 )
+@click.argument("extra_pytest_args", nargs=-1, type=click.UNPROCESSED)
+@option_python
+@option_python_versions
+@option_image_tag_for_verifying
+@option_image_name
+@option_pull
 @option_github_repository
 @option_github_token
+@option_run_in_parallel
+@option_parallelism
+@option_skip_cleanup
+@option_include_success_outputs
+@option_debug_resources
 @option_verbose
 @option_dry_run
-@click.argument("extra_pytest_args", nargs=-1, type=click.UNPROCESSED)
 def verify(
     python: str,
+    python_versions: str,
     github_repository: str,
     image_name: str,
     image_tag: str | None,
@@ -478,6 +529,11 @@ def verify(
     slim_image: bool,
     github_token: str,
     extra_pytest_args: tuple,
+    run_in_parallel: bool,
+    parallelism: int,
+    skip_cleanup: bool,
+    debug_resources: bool,
+    include_success_outputs: bool,
 ):
     """Verify Production image."""
     perform_environment_checks()
@@ -485,27 +541,54 @@ def verify(
         github_token=github_token,
         output=None,
     )
-    if image_name is None:
-        build_params = BuildProdParams(
+    if (pull or image_name) and run_in_parallel:
+        get_console().print(
+            "[error]You cannot use --pull,--image-name and --run-in-parallel 
at the same time. " "Exiting[/]"
+        )
+        sys.exit(1)
+    if run_in_parallel:
+        base_build_params = BuildProdParams(
             python=python,
-            image_tag=image_tag,
             github_repository=github_repository,
-            github_token=github_token,
+            image_tag=image_tag,
         )
-        image_name = build_params.airflow_image_name_with_tag
-    if pull:
-        check_remote_ghcr_io_commands()
-        command_to_run = ["docker", "pull", image_name]
-        run_command(command_to_run, check=True)
-    get_console().print(f"[info]Verifying PROD image: {image_name}[/]")
-    return_code, info = verify_an_image(
-        image_name=image_name,
-        output=None,
-        image_type="PROD",
-        extra_pytest_args=extra_pytest_args,
-        slim_image=slim_image,
-    )
-    sys.exit(return_code)
+        python_version_list = get_python_version_list(python_versions)
+        params_list: list[BuildProdParams] = []
+        for python in python_version_list:
+            build_params = deepcopy(base_build_params)
+            build_params.python = python
+            params_list.append(build_params)
+        run_verify_in_parallel(
+            image_params_list=params_list,
+            python_version_list=python_version_list,
+            extra_pytest_args=extra_pytest_args,
+            include_success_outputs=include_success_outputs,
+            parallelism=parallelism,
+            skip_cleanup=skip_cleanup,
+            debug_resources=debug_resources,
+        )
+    else:
+        if image_name is None:
+            build_params = BuildProdParams(
+                python=python,
+                image_tag=image_tag,
+                github_repository=github_repository,
+                github_token=github_token,
+            )
+            image_name = build_params.airflow_image_name_with_tag
+        if pull:
+            check_remote_ghcr_io_commands()
+            command_to_run = ["docker", "pull", image_name]
+            run_command(command_to_run, check=True)
+        get_console().print(f"[info]Verifying PROD image: {image_name}[/]")
+        return_code, info = verify_an_image(
+            image_name=image_name,
+            output=None,
+            image_type="PROD",
+            extra_pytest_args=extra_pytest_args,
+            slim_image=slim_image,
+        )
+        sys.exit(return_code)
 
 
 def clean_docker_context_files():
diff --git 
a/dev/breeze/src/airflow_breeze/commands/production_image_commands_config.py 
b/dev/breeze/src/airflow_breeze/commands/production_image_commands_config.py
index 52291268f8..9d20567537 100644
--- a/dev/breeze/src/airflow_breeze/commands/production_image_commands_config.py
+++ b/dev/breeze/src/airflow_breeze/commands/production_image_commands_config.py
@@ -157,6 +157,17 @@ PRODUCTION_IMAGE_TOOLS_PARAMETERS: dict[str, 
list[dict[str, str | list[str]]]] =
                 "--pull",
             ],
         },
+        {
+            "name": "Parallel running",
+            "options": [
+                "--run-in-parallel",
+                "--parallelism",
+                "--python-versions",
+                "--skip-cleanup",
+                "--debug-resources",
+                "--include-success-outputs",
+            ],
+        },
         {
             "name": "Github authentication",
             "options": [
diff --git a/dev/breeze/src/airflow_breeze/utils/run_tests.py 
b/dev/breeze/src/airflow_breeze/utils/run_tests.py
index d2ce641fe4..c3fd500f08 100644
--- a/dev/breeze/src/airflow_breeze/utils/run_tests.py
+++ b/dev/breeze/src/airflow_breeze/utils/run_tests.py
@@ -33,7 +33,7 @@ def verify_an_image(
     image_type: str,
     output: Output | None,
     slim_image: bool,
-    extra_pytest_args: tuple,
+    extra_pytest_args: tuple[str, ...],
 ) -> tuple[int, str]:
     command_result = run_command(
         ["docker", "inspect", image_name],
diff --git a/docker_tests/test_ci_image.py b/docker_tests/test_ci_image.py
index 2c3fabd7d9..7ec65c425c 100644
--- a/docker_tests/test_ci_image.py
+++ b/docker_tests/test_ci_image.py
@@ -22,12 +22,28 @@ from docker_tests.command_utils import run_command
 from docker_tests.docker_tests_utils import 
display_dependency_conflict_message, docker_image
 
 
-class TestPythonPackages:
-    def test_pip_dependencies_conflict(self):
-        try:
-            run_command(
-                ["docker", "run", "--rm", "--entrypoint", "/bin/bash", 
docker_image, "-c", "pip check"]
-            )
-        except subprocess.CalledProcessError as ex:
-            display_dependency_conflict_message()
-            raise ex
+def test_pip_dependencies_conflict():
+    try:
+        run_command(["docker", "run", "--rm", "--entrypoint", "/bin/bash", 
docker_image, "-c", "pip check"])
+    except subprocess.CalledProcessError as ex:
+        display_dependency_conflict_message()
+        raise ex
+
+
+def test_providers_present():
+    try:
+        run_command(
+            [
+                "docker",
+                "run",
+                "--rm",
+                "--entrypoint",
+                "/bin/bash",
+                docker_image,
+                "-c",
+                "airflow providers list",
+            ],
+        )
+    except subprocess.CalledProcessError as ex:
+        display_dependency_conflict_message()
+        raise ex
diff --git a/images/breeze/output_ci-image_verify.svg 
b/images/breeze/output_ci-image_verify.svg
index 42b327ef9c..cb247415e9 100644
--- a/images/breeze/output_ci-image_verify.svg
+++ b/images/breeze/output_ci-image_verify.svg
@@ -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 830.8" 
xmlns="http://www.w3.org/2000/svg";>
     <!-- Generated with Rich https://www.textualize.io -->
     <style>
 
@@ -43,7 +43,7 @@
 
     <defs>
     <clipPath id="breeze-ci-image-verify-clip-terminal">
-      <rect x="0" y="0" width="1463.0" height="511.4" />
+      <rect x="0" y="0" width="1463.0" height="779.8" />
     </clipPath>
     <clipPath id="breeze-ci-image-verify-line-0">
     <rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -105,9 +105,42 @@
 <clipPath id="breeze-ci-image-verify-line-19">
     <rect x="0" y="465.1" width="1464" height="24.65"/>
             </clipPath>
+<clipPath id="breeze-ci-image-verify-line-20">
+    <rect x="0" y="489.5" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-ci-image-verify-line-21">
+    <rect x="0" y="513.9" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-ci-image-verify-line-22">
+    <rect x="0" y="538.3" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-ci-image-verify-line-23">
+    <rect x="0" y="562.7" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-ci-image-verify-line-24">
+    <rect x="0" y="587.1" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-ci-image-verify-line-25">
+    <rect x="0" y="611.5" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-ci-image-verify-line-26">
+    <rect x="0" y="635.9" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-ci-image-verify-line-27">
+    <rect x="0" y="660.3" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-ci-image-verify-line-28">
+    <rect x="0" y="684.7" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-ci-image-verify-line-29">
+    <rect x="0" y="709.1" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-ci-image-verify-line-30">
+    <rect x="0" y="733.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="560.4" rx="8"/><text 
class="breeze-ci-image-verify-title" fill="#c5c8c6" text-anchor="middle" 
x="740" y="27">Command:&#160;ci-image&#160;verify</text>
+    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" 
x="1" y="1" width="1480" height="828.8" rx="8"/><text 
class="breeze-ci-image-verify-title" fill="#c5c8c6" text-anchor="middle" 
x="740" y="27">Command:&#160;ci-image&#160;verify</text>
             <g transform="translate(26,22)">
             <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
             <circle cx="22" cy="0" r="7" fill="#febc2e"/>
@@ -129,15 +162,26 @@
 </text><text class="breeze-ci-image-verify-r5" x="0" y="239.6" 
textLength="12.2" clip-path="url(#breeze-ci-image-verify-line-9)">│</text><text 
class="breeze-ci-image-verify-r4" x="24.4" y="239.6" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-9)">-</text><text 
class="breeze-ci-image-verify-r4" x="36.6" y="239.6" textLength="73.2" 
clip-path="url(#breeze-ci-image-verify-line-9)">-image</text><text 
class="breeze-ci-image-verify-r4" x="109.8" y="239.6" textLength="48.8" clip-p 
[...]
 </text><text class="breeze-ci-image-verify-r5" x="0" y="264" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-10)">│</text><text 
class="breeze-ci-image-verify-r4" x="24.4" y="264" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-10)">-</text><text 
class="breeze-ci-image-verify-r4" x="36.6" y="264" textLength="61" 
clip-path="url(#breeze-ci-image-verify-line-10)">-pull</text><text 
class="breeze-ci-image-verify-r1" x="244" y="264" textLength="646.6" 
clip-path="url( [...]
 </text><text class="breeze-ci-image-verify-r5" x="0" y="288.4" 
textLength="1464" 
clip-path="url(#breeze-ci-image-verify-line-11)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-verify-r1" x="1464" y="288.4" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-11)">
-</text><text class="breeze-ci-image-verify-r5" x="0" y="312.8" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-verify-line-12)">╭─</text><text 
class="breeze-ci-image-verify-r5" x="24.4" y="312.8" textLength="280.6" 
clip-path="url(#breeze-ci-image-verify-line-12)">&#160;Github&#160;authentication&#160;</text><text
 class="breeze-ci-image-verify-r5" x="305" y="312.8" textLength="1134.6" 
clip-path="url(#breeze-ci-image-verify-line-12)">──────────────────────────────────────────────────────
 [...]
-</text><text class="breeze-ci-image-verify-r5" x="0" y="337.2" 
textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-13)">│</text><text 
class="breeze-ci-image-verify-r4" x="24.4" y="337.2" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-13)">-</text><text 
class="breeze-ci-image-verify-r4" x="36.6" y="337.2" textLength="85.4" 
clip-path="url(#breeze-ci-image-verify-line-13)">-github</text><text 
class="breeze-ci-image-verify-r4" x="122" y="337.2" textLength="134.2" cli [...]
-</text><text class="breeze-ci-image-verify-r5" x="0" y="361.6" 
textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-14)">│</text><text 
class="breeze-ci-image-verify-r4" x="24.4" y="361.6" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-14)">-</text><text 
class="breeze-ci-image-verify-r4" x="36.6" y="361.6" textLength="85.4" 
clip-path="url(#breeze-ci-image-verify-line-14)">-github</text><text 
class="breeze-ci-image-verify-r4" x="122" y="361.6" textLength="73.2" clip [...]
-</text><text class="breeze-ci-image-verify-r5" x="0" y="386" textLength="1464" 
clip-path="url(#breeze-ci-image-verify-line-15)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-verify-r1" x="1464" y="386" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-15)">
-</text><text class="breeze-ci-image-verify-r5" x="0" y="410.4" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-verify-line-16)">╭─</text><text 
class="breeze-ci-image-verify-r5" x="24.4" y="410.4" textLength="195.2" 
clip-path="url(#breeze-ci-image-verify-line-16)">&#160;Common&#160;options&#160;</text><text
 class="breeze-ci-image-verify-r5" x="219.6" y="410.4" textLength="1220" 
clip-path="url(#breeze-ci-image-verify-line-16)">─────────────────────────────────────────────────────────────
 [...]
-</text><text class="breeze-ci-image-verify-r5" x="0" y="434.8" 
textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-17)">│</text><text 
class="breeze-ci-image-verify-r4" x="24.4" y="434.8" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-17)">-</text><text 
class="breeze-ci-image-verify-r4" x="36.6" y="434.8" textLength="97.6" 
clip-path="url(#breeze-ci-image-verify-line-17)">-verbose</text><text 
class="breeze-ci-image-verify-r6" x="158.6" y="434.8" textLength="24.4" c [...]
-</text><text class="breeze-ci-image-verify-r5" x="0" y="459.2" 
textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-18)">│</text><text 
class="breeze-ci-image-verify-r4" x="24.4" y="459.2" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-18)">-</text><text 
class="breeze-ci-image-verify-r4" x="36.6" y="459.2" textLength="48.8" 
clip-path="url(#breeze-ci-image-verify-line-18)">-dry</text><text 
class="breeze-ci-image-verify-r4" x="85.4" y="459.2" textLength="48.8" clip-p 
[...]
-</text><text class="breeze-ci-image-verify-r5" x="0" y="483.6" 
textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-19)">│</text><text 
class="breeze-ci-image-verify-r4" x="24.4" y="483.6" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-19)">-</text><text 
class="breeze-ci-image-verify-r4" x="36.6" y="483.6" textLength="61" 
clip-path="url(#breeze-ci-image-verify-line-19)">-help</text><text 
class="breeze-ci-image-verify-r6" x="158.6" y="483.6" textLength="24.4" clip-p 
[...]
-</text><text class="breeze-ci-image-verify-r5" x="0" y="508" textLength="1464" 
clip-path="url(#breeze-ci-image-verify-line-20)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-verify-r1" x="1464" y="508" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-20)">
+</text><text class="breeze-ci-image-verify-r5" x="0" y="312.8" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-verify-line-12)">╭─</text><text 
class="breeze-ci-image-verify-r5" x="24.4" y="312.8" textLength="219.6" 
clip-path="url(#breeze-ci-image-verify-line-12)">&#160;Parallel&#160;running&#160;</text><text
 class="breeze-ci-image-verify-r5" x="244" y="312.8" textLength="1195.6" 
clip-path="url(#breeze-ci-image-verify-line-12)">───────────────────────────────────────────────────────────
 [...]
+</text><text class="breeze-ci-image-verify-r5" x="0" y="337.2" 
textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-13)">│</text><text 
class="breeze-ci-image-verify-r4" x="24.4" y="337.2" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-13)">-</text><text 
class="breeze-ci-image-verify-r4" x="36.6" y="337.2" textLength="48.8" 
clip-path="url(#breeze-ci-image-verify-line-13)">-run</text><text 
class="breeze-ci-image-verify-r4" x="85.4" y="337.2" textLength="146.4" clip- 
[...]
+</text><text class="breeze-ci-image-verify-r5" x="0" y="361.6" 
textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-14)">│</text><text 
class="breeze-ci-image-verify-r4" x="24.4" y="361.6" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-14)">-</text><text 
class="breeze-ci-image-verify-r4" x="36.6" y="361.6" textLength="146.4" 
clip-path="url(#breeze-ci-image-verify-line-14)">-parallelism</text><text 
class="breeze-ci-image-verify-r1" x="378.2" y="361.6" textLength="91 [...]
+</text><text class="breeze-ci-image-verify-r5" x="0" y="386" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-15)">│</text><text 
class="breeze-ci-image-verify-r7" x="378.2" y="386" textLength="915" 
clip-path="url(#breeze-ci-image-verify-line-15)">(INTEGER&#160;RANGE)&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#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-image-verify-r5" x="0" y="410.4" 
textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-16)">│</text><text 
class="breeze-ci-image-verify-r5" x="378.2" y="410.4" textLength="915" 
clip-path="url(#breeze-ci-image-verify-line-16)">[default:&#160;4;&#160;1&lt;=x&lt;=8]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#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-image-verify-r5" x="0" y="434.8" 
textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-17)">│</text><text 
class="breeze-ci-image-verify-r4" x="24.4" y="434.8" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-17)">-</text><text 
class="breeze-ci-image-verify-r4" x="36.6" y="434.8" textLength="85.4" 
clip-path="url(#breeze-ci-image-verify-line-17)">-python</text><text 
class="breeze-ci-image-verify-r4" x="122" y="434.8" textLength="109.8" cli [...]
+</text><text class="breeze-ci-image-verify-r5" x="0" y="459.2" 
textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-18)">│</text><text 
class="breeze-ci-image-verify-r5" x="378.2" y="459.2" textLength="951.6" 
clip-path="url(#breeze-ci-image-verify-line-18)">[default:&#160;3.8&#160;3.9&#160;3.10&#160;3.11]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#16
 [...]
+</text><text class="breeze-ci-image-verify-r5" x="0" y="483.6" 
textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-19)">│</text><text 
class="breeze-ci-image-verify-r4" x="24.4" y="483.6" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-19)">-</text><text 
class="breeze-ci-image-verify-r4" x="36.6" y="483.6" textLength="61" 
clip-path="url(#breeze-ci-image-verify-line-19)">-skip</text><text 
class="breeze-ci-image-verify-r4" x="97.6" y="483.6" textLength="97.6" clip-pa 
[...]
+</text><text class="breeze-ci-image-verify-r5" x="0" y="508" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-20)">│</text><text 
class="breeze-ci-image-verify-r4" x="24.4" y="508" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-20)">-</text><text 
class="breeze-ci-image-verify-r4" x="36.6" y="508" textLength="73.2" 
clip-path="url(#breeze-ci-image-verify-line-20)">-debug</text><text 
class="breeze-ci-image-verify-r4" x="109.8" y="508" textLength="122" 
clip-path="u [...]
+</text><text class="breeze-ci-image-verify-r5" x="0" y="532.4" 
textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-21)">│</text><text 
class="breeze-ci-image-verify-r4" x="24.4" y="532.4" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-21)">-</text><text 
class="breeze-ci-image-verify-r4" x="36.6" y="532.4" textLength="97.6" 
clip-path="url(#breeze-ci-image-verify-line-21)">-include</text><text 
class="breeze-ci-image-verify-r4" x="134.2" y="532.4" textLength="195.2"  [...]
+</text><text class="breeze-ci-image-verify-r5" x="0" y="556.8" 
textLength="1464" 
clip-path="url(#breeze-ci-image-verify-line-22)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-verify-r1" x="1464" y="556.8" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-22)">
+</text><text class="breeze-ci-image-verify-r5" x="0" y="581.2" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-verify-line-23)">╭─</text><text 
class="breeze-ci-image-verify-r5" x="24.4" y="581.2" textLength="280.6" 
clip-path="url(#breeze-ci-image-verify-line-23)">&#160;Github&#160;authentication&#160;</text><text
 class="breeze-ci-image-verify-r5" x="305" y="581.2" textLength="1134.6" 
clip-path="url(#breeze-ci-image-verify-line-23)">──────────────────────────────────────────────────────
 [...]
+</text><text class="breeze-ci-image-verify-r5" x="0" y="605.6" 
textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-24)">│</text><text 
class="breeze-ci-image-verify-r4" x="24.4" y="605.6" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-24)">-</text><text 
class="breeze-ci-image-verify-r4" x="36.6" y="605.6" textLength="85.4" 
clip-path="url(#breeze-ci-image-verify-line-24)">-github</text><text 
class="breeze-ci-image-verify-r4" x="122" y="605.6" textLength="134.2" cli [...]
+</text><text class="breeze-ci-image-verify-r5" x="0" y="630" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-25)">│</text><text 
class="breeze-ci-image-verify-r4" x="24.4" y="630" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-25)">-</text><text 
class="breeze-ci-image-verify-r4" x="36.6" y="630" textLength="85.4" 
clip-path="url(#breeze-ci-image-verify-line-25)">-github</text><text 
class="breeze-ci-image-verify-r4" x="122" y="630" textLength="73.2" 
clip-path="u [...]
+</text><text class="breeze-ci-image-verify-r5" x="0" y="654.4" 
textLength="1464" 
clip-path="url(#breeze-ci-image-verify-line-26)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-verify-r1" x="1464" y="654.4" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-26)">
+</text><text class="breeze-ci-image-verify-r5" x="0" y="678.8" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-verify-line-27)">╭─</text><text 
class="breeze-ci-image-verify-r5" x="24.4" y="678.8" textLength="195.2" 
clip-path="url(#breeze-ci-image-verify-line-27)">&#160;Common&#160;options&#160;</text><text
 class="breeze-ci-image-verify-r5" x="219.6" y="678.8" textLength="1220" 
clip-path="url(#breeze-ci-image-verify-line-27)">─────────────────────────────────────────────────────────────
 [...]
+</text><text class="breeze-ci-image-verify-r5" x="0" y="703.2" 
textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-28)">│</text><text 
class="breeze-ci-image-verify-r4" x="24.4" y="703.2" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-28)">-</text><text 
class="breeze-ci-image-verify-r4" x="36.6" y="703.2" textLength="97.6" 
clip-path="url(#breeze-ci-image-verify-line-28)">-verbose</text><text 
class="breeze-ci-image-verify-r6" x="158.6" y="703.2" textLength="24.4" c [...]
+</text><text class="breeze-ci-image-verify-r5" x="0" y="727.6" 
textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-29)">│</text><text 
class="breeze-ci-image-verify-r4" x="24.4" y="727.6" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-29)">-</text><text 
class="breeze-ci-image-verify-r4" x="36.6" y="727.6" textLength="48.8" 
clip-path="url(#breeze-ci-image-verify-line-29)">-dry</text><text 
class="breeze-ci-image-verify-r4" x="85.4" y="727.6" textLength="48.8" clip-p 
[...]
+</text><text class="breeze-ci-image-verify-r5" x="0" y="752" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-30)">│</text><text 
class="breeze-ci-image-verify-r4" x="24.4" y="752" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-30)">-</text><text 
class="breeze-ci-image-verify-r4" x="36.6" y="752" textLength="61" 
clip-path="url(#breeze-ci-image-verify-line-30)">-help</text><text 
class="breeze-ci-image-verify-r6" x="158.6" y="752" textLength="24.4" 
clip-path="url [...]
+</text><text class="breeze-ci-image-verify-r5" x="0" y="776.4" 
textLength="1464" 
clip-path="url(#breeze-ci-image-verify-line-31)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-verify-r1" x="1464" y="776.4" textLength="12.2" 
clip-path="url(#breeze-ci-image-verify-line-31)">
 </text>
     </g>
     </g>
diff --git a/images/breeze/output_ci-image_verify.txt 
b/images/breeze/output_ci-image_verify.txt
index 6b16a49525..f454443f68 100644
--- a/images/breeze/output_ci-image_verify.txt
+++ b/images/breeze/output_ci-image_verify.txt
@@ -1 +1 @@
-c90dc7e20fce2351eb89d8d1ebbd35e7
+707a149f99bd49d37be5b8d0db844d69
diff --git a/images/breeze/output_prod-image_verify.svg 
b/images/breeze/output_prod-image_verify.svg
index 508fcbd34c..5633e0226c 100644
--- a/images/breeze/output_prod-image_verify.svg
+++ b/images/breeze/output_prod-image_verify.svg
@@ -1,4 +1,4 @@
-<svg class="rich-terminal" viewBox="0 0 1482 586.8" 
xmlns="http://www.w3.org/2000/svg";>
+<svg class="rich-terminal" viewBox="0 0 1482 855.1999999999999" 
xmlns="http://www.w3.org/2000/svg";>
     <!-- Generated with Rich https://www.textualize.io -->
     <style>
 
@@ -43,7 +43,7 @@
 
     <defs>
     <clipPath id="breeze-prod-image-verify-clip-terminal">
-      <rect x="0" y="0" width="1463.0" height="535.8" />
+      <rect x="0" y="0" width="1463.0" height="804.1999999999999" />
     </clipPath>
     <clipPath id="breeze-prod-image-verify-line-0">
     <rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -108,9 +108,42 @@
 <clipPath id="breeze-prod-image-verify-line-20">
     <rect x="0" y="489.5" width="1464" height="24.65"/>
             </clipPath>
+<clipPath id="breeze-prod-image-verify-line-21">
+    <rect x="0" y="513.9" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-prod-image-verify-line-22">
+    <rect x="0" y="538.3" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-prod-image-verify-line-23">
+    <rect x="0" y="562.7" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-prod-image-verify-line-24">
+    <rect x="0" y="587.1" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-prod-image-verify-line-25">
+    <rect x="0" y="611.5" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-prod-image-verify-line-26">
+    <rect x="0" y="635.9" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-prod-image-verify-line-27">
+    <rect x="0" y="660.3" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-prod-image-verify-line-28">
+    <rect x="0" y="684.7" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-prod-image-verify-line-29">
+    <rect x="0" y="709.1" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-prod-image-verify-line-30">
+    <rect x="0" y="733.5" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-prod-image-verify-line-31">
+    <rect x="0" y="757.9" 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="584.8" rx="8"/><text 
class="breeze-prod-image-verify-title" fill="#c5c8c6" text-anchor="middle" 
x="740" y="27">Command:&#160;prod-image&#160;verify</text>
+    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" 
x="1" y="1" width="1480" height="853.2" rx="8"/><text 
class="breeze-prod-image-verify-title" fill="#c5c8c6" text-anchor="middle" 
x="740" y="27">Command:&#160;prod-image&#160;verify</text>
             <g transform="translate(26,22)">
             <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
             <circle cx="22" cy="0" r="7" fill="#febc2e"/>
@@ -133,15 +166,26 @@
 </text><text class="breeze-prod-image-verify-r5" x="0" y="264" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-10)">│</text><text 
class="breeze-prod-image-verify-r4" x="24.4" y="264" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-10)">-</text><text 
class="breeze-prod-image-verify-r4" x="36.6" y="264" textLength="73.2" 
clip-path="url(#breeze-prod-image-verify-line-10)">-image</text><text 
class="breeze-prod-image-verify-r4" x="109.8" y="264" textLength="48. [...]
 </text><text class="breeze-prod-image-verify-r5" x="0" y="288.4" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-11)">│</text><text 
class="breeze-prod-image-verify-r4" x="24.4" y="288.4" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-11)">-</text><text 
class="breeze-prod-image-verify-r4" x="36.6" y="288.4" textLength="61" 
clip-path="url(#breeze-prod-image-verify-line-11)">-pull</text><text 
class="breeze-prod-image-verify-r1" x="244" y="288.4" textLength=" [...]
 </text><text class="breeze-prod-image-verify-r5" x="0" y="312.8" 
textLength="1464" 
clip-path="url(#breeze-prod-image-verify-line-12)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-prod-image-verify-r1" x="1464" y="312.8" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-12)">
-</text><text class="breeze-prod-image-verify-r5" x="0" y="337.2" 
textLength="24.4" 
clip-path="url(#breeze-prod-image-verify-line-13)">╭─</text><text 
class="breeze-prod-image-verify-r5" x="24.4" y="337.2" textLength="280.6" 
clip-path="url(#breeze-prod-image-verify-line-13)">&#160;Github&#160;authentication&#160;</text><text
 class="breeze-prod-image-verify-r5" x="305" y="337.2" textLength="1134.6" 
clip-path="url(#breeze-prod-image-verify-line-13)">──────────────────────────────────────────
 [...]
-</text><text class="breeze-prod-image-verify-r5" x="0" y="361.6" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-14)">│</text><text 
class="breeze-prod-image-verify-r4" x="24.4" y="361.6" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-14)">-</text><text 
class="breeze-prod-image-verify-r4" x="36.6" y="361.6" textLength="85.4" 
clip-path="url(#breeze-prod-image-verify-line-14)">-github</text><text 
class="breeze-prod-image-verify-r4" x="122" y="361.6" textLeng [...]
-</text><text class="breeze-prod-image-verify-r5" x="0" y="386" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-15)">│</text><text 
class="breeze-prod-image-verify-r4" x="24.4" y="386" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-15)">-</text><text 
class="breeze-prod-image-verify-r4" x="36.6" y="386" textLength="85.4" 
clip-path="url(#breeze-prod-image-verify-line-15)">-github</text><text 
class="breeze-prod-image-verify-r4" x="122" y="386" textLength="73.2 [...]
-</text><text class="breeze-prod-image-verify-r5" x="0" y="410.4" 
textLength="1464" 
clip-path="url(#breeze-prod-image-verify-line-16)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-prod-image-verify-r1" x="1464" y="410.4" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-16)">
-</text><text class="breeze-prod-image-verify-r5" x="0" y="434.8" 
textLength="24.4" 
clip-path="url(#breeze-prod-image-verify-line-17)">╭─</text><text 
class="breeze-prod-image-verify-r5" x="24.4" y="434.8" textLength="195.2" 
clip-path="url(#breeze-prod-image-verify-line-17)">&#160;Common&#160;options&#160;</text><text
 class="breeze-prod-image-verify-r5" x="219.6" y="434.8" textLength="1220" 
clip-path="url(#breeze-prod-image-verify-line-17)">─────────────────────────────────────────────────
 [...]
-</text><text class="breeze-prod-image-verify-r5" x="0" y="459.2" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-18)">│</text><text 
class="breeze-prod-image-verify-r4" x="24.4" y="459.2" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-18)">-</text><text 
class="breeze-prod-image-verify-r4" x="36.6" y="459.2" textLength="97.6" 
clip-path="url(#breeze-prod-image-verify-line-18)">-verbose</text><text 
class="breeze-prod-image-verify-r6" x="158.6" y="459.2" textL [...]
-</text><text class="breeze-prod-image-verify-r5" x="0" y="483.6" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-19)">│</text><text 
class="breeze-prod-image-verify-r4" x="24.4" y="483.6" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-19)">-</text><text 
class="breeze-prod-image-verify-r4" x="36.6" y="483.6" textLength="48.8" 
clip-path="url(#breeze-prod-image-verify-line-19)">-dry</text><text 
class="breeze-prod-image-verify-r4" x="85.4" y="483.6" textLength [...]
-</text><text class="breeze-prod-image-verify-r5" x="0" y="508" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-20)">│</text><text 
class="breeze-prod-image-verify-r4" x="24.4" y="508" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-20)">-</text><text 
class="breeze-prod-image-verify-r4" x="36.6" y="508" textLength="61" 
clip-path="url(#breeze-prod-image-verify-line-20)">-help</text><text 
class="breeze-prod-image-verify-r6" x="158.6" y="508" textLength="24.4"  [...]
-</text><text class="breeze-prod-image-verify-r5" x="0" y="532.4" 
textLength="1464" 
clip-path="url(#breeze-prod-image-verify-line-21)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-prod-image-verify-r1" x="1464" y="532.4" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-21)">
+</text><text class="breeze-prod-image-verify-r5" x="0" y="337.2" 
textLength="24.4" 
clip-path="url(#breeze-prod-image-verify-line-13)">╭─</text><text 
class="breeze-prod-image-verify-r5" x="24.4" y="337.2" textLength="219.6" 
clip-path="url(#breeze-prod-image-verify-line-13)">&#160;Parallel&#160;running&#160;</text><text
 class="breeze-prod-image-verify-r5" x="244" y="337.2" textLength="1195.6" 
clip-path="url(#breeze-prod-image-verify-line-13)">───────────────────────────────────────────────
 [...]
+</text><text class="breeze-prod-image-verify-r5" x="0" y="361.6" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-14)">│</text><text 
class="breeze-prod-image-verify-r4" x="24.4" y="361.6" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-14)">-</text><text 
class="breeze-prod-image-verify-r4" x="36.6" y="361.6" textLength="48.8" 
clip-path="url(#breeze-prod-image-verify-line-14)">-run</text><text 
class="breeze-prod-image-verify-r4" x="85.4" y="361.6" textLength [...]
+</text><text class="breeze-prod-image-verify-r5" x="0" y="386" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-15)">│</text><text 
class="breeze-prod-image-verify-r4" x="24.4" y="386" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-15)">-</text><text 
class="breeze-prod-image-verify-r4" x="36.6" y="386" textLength="146.4" 
clip-path="url(#breeze-prod-image-verify-line-15)">-parallelism</text><text 
class="breeze-prod-image-verify-r1" x="378.2" y="386" textLeng [...]
+</text><text class="breeze-prod-image-verify-r5" x="0" y="410.4" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-16)">│</text><text 
class="breeze-prod-image-verify-r7" x="378.2" y="410.4" textLength="915" 
clip-path="url(#breeze-prod-image-verify-line-16)">(INTEGER&#160;RANGE)&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#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-prod-image-verify-r5" x="0" y="434.8" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-17)">│</text><text 
class="breeze-prod-image-verify-r5" x="378.2" y="434.8" textLength="915" 
clip-path="url(#breeze-prod-image-verify-line-17)">[default:&#160;4;&#160;1&lt;=x&lt;=8]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#1
 [...]
+</text><text class="breeze-prod-image-verify-r5" x="0" y="459.2" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-18)">│</text><text 
class="breeze-prod-image-verify-r4" x="24.4" y="459.2" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-18)">-</text><text 
class="breeze-prod-image-verify-r4" x="36.6" y="459.2" textLength="85.4" 
clip-path="url(#breeze-prod-image-verify-line-18)">-python</text><text 
class="breeze-prod-image-verify-r4" x="122" y="459.2" textLeng [...]
+</text><text class="breeze-prod-image-verify-r5" x="0" y="483.6" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-19)">│</text><text 
class="breeze-prod-image-verify-r5" x="378.2" y="483.6" textLength="951.6" 
clip-path="url(#breeze-prod-image-verify-line-19)">[default:&#160;3.8&#160;3.9&#160;3.10&#160;3.11]&#160;&#160;&#160;&#160;&#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-prod-image-verify-r5" x="0" y="508" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-20)">│</text><text 
class="breeze-prod-image-verify-r4" x="24.4" y="508" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-20)">-</text><text 
class="breeze-prod-image-verify-r4" x="36.6" y="508" textLength="61" 
clip-path="url(#breeze-prod-image-verify-line-20)">-skip</text><text 
class="breeze-prod-image-verify-r4" x="97.6" y="508" textLength="97.6" c [...]
+</text><text class="breeze-prod-image-verify-r5" x="0" y="532.4" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-21)">│</text><text 
class="breeze-prod-image-verify-r4" x="24.4" y="532.4" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-21)">-</text><text 
class="breeze-prod-image-verify-r4" x="36.6" y="532.4" textLength="73.2" 
clip-path="url(#breeze-prod-image-verify-line-21)">-debug</text><text 
class="breeze-prod-image-verify-r4" x="109.8" y="532.4" textLen [...]
+</text><text class="breeze-prod-image-verify-r5" x="0" y="556.8" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-22)">│</text><text 
class="breeze-prod-image-verify-r4" x="24.4" y="556.8" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-22)">-</text><text 
class="breeze-prod-image-verify-r4" x="36.6" y="556.8" textLength="97.6" 
clip-path="url(#breeze-prod-image-verify-line-22)">-include</text><text 
class="breeze-prod-image-verify-r4" x="134.2" y="556.8" textL [...]
+</text><text class="breeze-prod-image-verify-r5" x="0" y="581.2" 
textLength="1464" 
clip-path="url(#breeze-prod-image-verify-line-23)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-prod-image-verify-r1" x="1464" y="581.2" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-23)">
+</text><text class="breeze-prod-image-verify-r5" x="0" y="605.6" 
textLength="24.4" 
clip-path="url(#breeze-prod-image-verify-line-24)">╭─</text><text 
class="breeze-prod-image-verify-r5" x="24.4" y="605.6" textLength="280.6" 
clip-path="url(#breeze-prod-image-verify-line-24)">&#160;Github&#160;authentication&#160;</text><text
 class="breeze-prod-image-verify-r5" x="305" y="605.6" textLength="1134.6" 
clip-path="url(#breeze-prod-image-verify-line-24)">──────────────────────────────────────────
 [...]
+</text><text class="breeze-prod-image-verify-r5" x="0" y="630" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-25)">│</text><text 
class="breeze-prod-image-verify-r4" x="24.4" y="630" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-25)">-</text><text 
class="breeze-prod-image-verify-r4" x="36.6" y="630" textLength="85.4" 
clip-path="url(#breeze-prod-image-verify-line-25)">-github</text><text 
class="breeze-prod-image-verify-r4" x="122" y="630" textLength="134. [...]
+</text><text class="breeze-prod-image-verify-r5" x="0" y="654.4" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-26)">│</text><text 
class="breeze-prod-image-verify-r4" x="24.4" y="654.4" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-26)">-</text><text 
class="breeze-prod-image-verify-r4" x="36.6" y="654.4" textLength="85.4" 
clip-path="url(#breeze-prod-image-verify-line-26)">-github</text><text 
class="breeze-prod-image-verify-r4" x="122" y="654.4" textLeng [...]
+</text><text class="breeze-prod-image-verify-r5" x="0" y="678.8" 
textLength="1464" 
clip-path="url(#breeze-prod-image-verify-line-27)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-prod-image-verify-r1" x="1464" y="678.8" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-27)">
+</text><text class="breeze-prod-image-verify-r5" x="0" y="703.2" 
textLength="24.4" 
clip-path="url(#breeze-prod-image-verify-line-28)">╭─</text><text 
class="breeze-prod-image-verify-r5" x="24.4" y="703.2" textLength="195.2" 
clip-path="url(#breeze-prod-image-verify-line-28)">&#160;Common&#160;options&#160;</text><text
 class="breeze-prod-image-verify-r5" x="219.6" y="703.2" textLength="1220" 
clip-path="url(#breeze-prod-image-verify-line-28)">─────────────────────────────────────────────────
 [...]
+</text><text class="breeze-prod-image-verify-r5" x="0" y="727.6" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-29)">│</text><text 
class="breeze-prod-image-verify-r4" x="24.4" y="727.6" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-29)">-</text><text 
class="breeze-prod-image-verify-r4" x="36.6" y="727.6" textLength="97.6" 
clip-path="url(#breeze-prod-image-verify-line-29)">-verbose</text><text 
class="breeze-prod-image-verify-r6" x="158.6" y="727.6" textL [...]
+</text><text class="breeze-prod-image-verify-r5" x="0" y="752" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-30)">│</text><text 
class="breeze-prod-image-verify-r4" x="24.4" y="752" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-30)">-</text><text 
class="breeze-prod-image-verify-r4" x="36.6" y="752" textLength="48.8" 
clip-path="url(#breeze-prod-image-verify-line-30)">-dry</text><text 
class="breeze-prod-image-verify-r4" x="85.4" y="752" textLength="48.8"  [...]
+</text><text class="breeze-prod-image-verify-r5" x="0" y="776.4" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-31)">│</text><text 
class="breeze-prod-image-verify-r4" x="24.4" y="776.4" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-31)">-</text><text 
class="breeze-prod-image-verify-r4" x="36.6" y="776.4" textLength="61" 
clip-path="url(#breeze-prod-image-verify-line-31)">-help</text><text 
class="breeze-prod-image-verify-r6" x="158.6" y="776.4" textLength [...]
+</text><text class="breeze-prod-image-verify-r5" x="0" y="800.8" 
textLength="1464" 
clip-path="url(#breeze-prod-image-verify-line-32)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-prod-image-verify-r1" x="1464" y="800.8" textLength="12.2" 
clip-path="url(#breeze-prod-image-verify-line-32)">
 </text>
     </g>
     </g>
diff --git a/images/breeze/output_prod-image_verify.txt 
b/images/breeze/output_prod-image_verify.txt
index 2b87281a7f..8301a9d978 100644
--- a/images/breeze/output_prod-image_verify.txt
+++ b/images/breeze/output_prod-image_verify.txt
@@ -1 +1 @@
-bd2b78738a7c388dbad6076c41a9f906
+3d7fdd3877862ce9bfad8018a5282745

Reply via email to