potiuk commented on code in PR #74311:
URL: https://github.com/apache/airflow/pull/74311#discussion_r4307908256


##########
.github/workflows/dependency-upgrade-report.yml:
##########
@@ -0,0 +1,167 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+---
+name: Dependency upgrade report
+# Both the job gate and the API validation reject PRs and foreign 
repositories. Only
+# completed main schedule/dispatch runs are executed, with a read-only token 
and no secrets.
+"on":  # zizmor: ignore[dangerous-triggers]
+  workflow_run:
+    workflows: ["Tests (AMD)"]
+    types: [completed]
+    branches: [main]
+permissions:
+  contents: read
+  actions: read
+concurrency:
+  group: dependency-report-${{ github.event.workflow_run.id }}
+  cancel-in-progress: false
+jobs:
+  plan:
+    runs-on: ubuntu-slim
+    timeout-minutes: 5
+    if: >-
+      github.event.workflow_run.head_repository.full_name == github.repository 
&&
+      (github.event.workflow_run.event == 'schedule' ||
+       github.event.workflow_run.event == 'workflow_dispatch')
+    outputs:
+      matrix: ${{ steps.plan.outputs.matrix }}
+      has-reports: ${{ steps.plan.outputs.has-reports }}
+      source-sha: ${{ steps.plan.outputs.source-sha }}
+      source-run-id: ${{ steps.plan.outputs.source-run-id }}
+    steps:
+      - name: "Select inputs from the original canary run"
+        id: plan
+        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3  
# v9.0.0
+        env:
+          SOURCE_RUN_ID: ${{ github.event.workflow_run.id }}
+        with:
+          script: |
+            const rawId = process.env.SOURCE_RUN_ID;
+            if (!/^[1-9][0-9]*$/.test(rawId)) throw new Error('Invalid source 
run ID');
+            const run_id = Number(rawId);
+            if (!Number.isSafeInteger(run_id)) throw new Error('Invalid source 
run ID');
+            const repo = context.repo;
+            const { data: run } = await github.rest.actions.getWorkflowRun({ 
...repo, run_id });
+            if (run.head_repository.full_name !== `${repo.owner}/${repo.repo}` 
||
+                run.head_branch !== 'main' || run.path !== 
'.github/workflows/ci-amd.yml' ||
+                !['schedule', 'workflow_dispatch'].includes(run.event) || 
run.status !== 'completed') {
+              throw new Error('Only completed main AMD canary runs are 
supported');
+            }
+            const artifacts = await 
github.paginate(github.rest.actions.listWorkflowRunArtifacts,
+              { ...repo, run_id, per_page: 100 });
+            const include = [];
+            const inputName = new 
RegExp('^dependency-report-inputs-(3\\.[0-9]+)-' +
+              
'(constraints(?:-source-providers|-no-providers)?)-(true|false)$');
+            for (const input of artifacts) {
+              const match = inputName.exec(input.name);
+              if (!match) continue;
+              const [, python, mode, useUv] = match;
+              const image = artifacts.find(a => a.name === 
`ci-image-save-v3-linux_amd64-${python}-main`);
+              if (input.expired || !image || image.expired) {
+                throw new Error(`Original inputs or CI image unavailable for 
${python}:${mode}`);
+              }

Review Comment:
   **One missing artifact stops every report.** In practice that looks likely: 
today's 02:00 `main` canary (run 39088484466) built CI images for 3.11 to 3.14, 
but 15 hours later only `ci-image-save-v3-linux_amd64-3.12-main` is still 
listed on that run. The stash appears to keep only the newest artifact per key 
and branch, so by the time a report runs, the other versions' images may 
already be gone. Skipping the unavailable entries (and listing them in the 
summary) keeps the reports that can still be built; 
`test_plan_does_not_substitute_newer_artifacts` would then assert the entry is 
skipped. Longer term, saving the image under a dedicated artifact name that 
later stashes don't replace would make the report independent of that.
   
   ```suggestion
                 if (input.expired || !image || image.expired) {
                   core.warning(`Skipping ${python}:${mode}: original inputs or 
CI image unavailable`);
                   continue;
                 }
   ```



##########
.github/workflows/dependency-upgrade-report.yml:
##########
@@ -0,0 +1,167 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+---
+name: Dependency upgrade report
+# Both the job gate and the API validation reject PRs and foreign 
repositories. Only
+# completed main schedule/dispatch runs are executed, with a read-only token 
and no secrets.
+"on":  # zizmor: ignore[dangerous-triggers]
+  workflow_run:
+    workflows: ["Tests (AMD)"]
+    types: [completed]
+    branches: [main]
+permissions:
+  contents: read
+  actions: read
+concurrency:
+  group: dependency-report-${{ github.event.workflow_run.id }}
+  cancel-in-progress: false

Review Comment:
   Re-running the source canary (e.g. one flaky job) fires `workflow_run` again 
with the same run id, and with `cancel-in-progress: false` that queues a second 
full report from the same inputs.



##########
.github/workflows/dependency-upgrade-report.yml:
##########
@@ -0,0 +1,167 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+---
+name: Dependency upgrade report
+# Both the job gate and the API validation reject PRs and foreign 
repositories. Only
+# completed main schedule/dispatch runs are executed, with a read-only token 
and no secrets.
+"on":  # zizmor: ignore[dangerous-triggers]
+  workflow_run:
+    workflows: ["Tests (AMD)"]
+    types: [completed]
+    branches: [main]
+permissions:
+  contents: read
+  actions: read
+concurrency:
+  group: dependency-report-${{ github.event.workflow_run.id }}
+  cancel-in-progress: false
+jobs:
+  plan:
+    runs-on: ubuntu-slim
+    timeout-minutes: 5
+    if: >-
+      github.event.workflow_run.head_repository.full_name == github.repository 
&&
+      (github.event.workflow_run.event == 'schedule' ||
+       github.event.workflow_run.event == 'workflow_dispatch')
+    outputs:
+      matrix: ${{ steps.plan.outputs.matrix }}
+      has-reports: ${{ steps.plan.outputs.has-reports }}
+      source-sha: ${{ steps.plan.outputs.source-sha }}
+      source-run-id: ${{ steps.plan.outputs.source-run-id }}
+    steps:
+      - name: "Select inputs from the original canary run"
+        id: plan
+        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3  
# v9.0.0
+        env:
+          SOURCE_RUN_ID: ${{ github.event.workflow_run.id }}
+        with:
+          script: |
+            const rawId = process.env.SOURCE_RUN_ID;
+            if (!/^[1-9][0-9]*$/.test(rawId)) throw new Error('Invalid source 
run ID');
+            const run_id = Number(rawId);
+            if (!Number.isSafeInteger(run_id)) throw new Error('Invalid source 
run ID');
+            const repo = context.repo;
+            const { data: run } = await github.rest.actions.getWorkflowRun({ 
...repo, run_id });
+            if (run.head_repository.full_name !== `${repo.owner}/${repo.repo}` 
||
+                run.head_branch !== 'main' || run.path !== 
'.github/workflows/ci-amd.yml' ||
+                !['schedule', 'workflow_dispatch'].includes(run.event) || 
run.status !== 'completed') {
+              throw new Error('Only completed main AMD canary runs are 
supported');
+            }
+            const artifacts = await 
github.paginate(github.rest.actions.listWorkflowRunArtifacts,
+              { ...repo, run_id, per_page: 100 });
+            const include = [];
+            const inputName = new 
RegExp('^dependency-report-inputs-(3\\.[0-9]+)-' +
+              
'(constraints(?:-source-providers|-no-providers)?)-(true|false)$');
+            for (const input of artifacts) {
+              const match = inputName.exec(input.name);
+              if (!match) continue;
+              const [, python, mode, useUv] = match;
+              const image = artifacts.find(a => a.name === 
`ci-image-save-v3-linux_amd64-${python}-main`);
+              if (input.expired || !image || image.expired) {
+                throw new Error(`Original inputs or CI image unavailable for 
${python}:${mode}`);
+              }
+              include.push({ python, mode, 'use-uv': useUv,
+                'inputs-id': input.id, 'image-id': image.id });
+            }
+            core.setOutput('matrix', JSON.stringify({ include }));
+            core.setOutput('has-reports', include.length > 0 ? 'true' : 
'false');
+            core.setOutput('source-sha', run.head_sha);
+            core.setOutput('source-run-id', rawId);
+            if (!include.length) {
+              if (Number(process.env.GITHUB_RUN_ATTEMPT) > 1) {

Review Comment:
   A re-run of a report whose canary saved no inputs fails hard here, while 
attempt 1 of the same run only posts a notice. That wrongly suggests inputs 
once existed and expired.



##########
.github/workflows/dependency-upgrade-report.yml:
##########
@@ -0,0 +1,167 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+---
+name: Dependency upgrade report
+# Both the job gate and the API validation reject PRs and foreign 
repositories. Only
+# completed main schedule/dispatch runs are executed, with a read-only token 
and no secrets.
+"on":  # zizmor: ignore[dangerous-triggers]
+  workflow_run:
+    workflows: ["Tests (AMD)"]
+    types: [completed]
+    branches: [main]
+permissions:
+  contents: read
+  actions: read
+concurrency:
+  group: dependency-report-${{ github.event.workflow_run.id }}
+  cancel-in-progress: false
+jobs:
+  plan:
+    runs-on: ubuntu-slim
+    timeout-minutes: 5
+    if: >-
+      github.event.workflow_run.head_repository.full_name == github.repository 
&&
+      (github.event.workflow_run.event == 'schedule' ||
+       github.event.workflow_run.event == 'workflow_dispatch')
+    outputs:
+      matrix: ${{ steps.plan.outputs.matrix }}
+      has-reports: ${{ steps.plan.outputs.has-reports }}
+      source-sha: ${{ steps.plan.outputs.source-sha }}
+      source-run-id: ${{ steps.plan.outputs.source-run-id }}
+    steps:
+      - name: "Select inputs from the original canary run"
+        id: plan
+        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3  
# v9.0.0
+        env:
+          SOURCE_RUN_ID: ${{ github.event.workflow_run.id }}
+        with:
+          script: |
+            const rawId = process.env.SOURCE_RUN_ID;
+            if (!/^[1-9][0-9]*$/.test(rawId)) throw new Error('Invalid source 
run ID');
+            const run_id = Number(rawId);
+            if (!Number.isSafeInteger(run_id)) throw new Error('Invalid source 
run ID');
+            const repo = context.repo;
+            const { data: run } = await github.rest.actions.getWorkflowRun({ 
...repo, run_id });
+            if (run.head_repository.full_name !== `${repo.owner}/${repo.repo}` 
||
+                run.head_branch !== 'main' || run.path !== 
'.github/workflows/ci-amd.yml' ||
+                !['schedule', 'workflow_dispatch'].includes(run.event) || 
run.status !== 'completed') {
+              throw new Error('Only completed main AMD canary runs are 
supported');
+            }
+            const artifacts = await 
github.paginate(github.rest.actions.listWorkflowRunArtifacts,
+              { ...repo, run_id, per_page: 100 });
+            const include = [];
+            const inputName = new 
RegExp('^dependency-report-inputs-(3\\.[0-9]+)-' +
+              
'(constraints(?:-source-providers|-no-providers)?)-(true|false)$');
+            for (const input of artifacts) {
+              const match = inputName.exec(input.name);
+              if (!match) continue;
+              const [, python, mode, useUv] = match;
+              const image = artifacts.find(a => a.name === 
`ci-image-save-v3-linux_amd64-${python}-main`);
+              if (input.expired || !image || image.expired) {
+                throw new Error(`Original inputs or CI image unavailable for 
${python}:${mode}`);
+              }
+              include.push({ python, mode, 'use-uv': useUv,
+                'inputs-id': input.id, 'image-id': image.id });
+            }
+            core.setOutput('matrix', JSON.stringify({ include }));
+            core.setOutput('has-reports', include.length > 0 ? 'true' : 
'false');
+            core.setOutput('source-sha', run.head_sha);
+            core.setOutput('source-run-id', rawId);
+            if (!include.length) {
+              if (Number(process.env.GITHUB_RUN_ATTEMPT) > 1) {
+                throw new Error('Saved report inputs are missing or expired');
+              }
+              core.notice('No saved report inputs: this run did not request 
independent diagnostics');
+            }
+            await core.summary.addHeading('Dependency upgrade diagnostics')
+              .addLink('Source canary run', run.html_url)
+              .addRaw(`\n\nSource revision: ${run.head_sha}\n\nReports: 
${include.length}\n`)
+              .write();
+
+  explain:
+    needs: plan
+    if: needs.plan.outputs.has-reports == 'true'
+    name: "Explain ${{ matrix.python }}:${{ matrix.mode }}"
+    runs-on: ubuntu-22.04
+    timeout-minutes: 90
+    strategy:
+      fail-fast: false
+      max-parallel: 3
+      matrix: ${{ fromJSON(needs.plan.outputs.matrix) }}
+    env:
+      GITHUB_REPOSITORY: ${{ github.repository }}
+      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+      GITHUB_USERNAME: ${{ github.actor }}
+      VERBOSE: "false"
+      USE_UV: ${{ matrix.use-uv }}
+    steps:
+      - name: "Checkout the tested revision"
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1  # 
v7.0.1
+        with:
+          ref: ${{ needs.plan.outputs.source-sha }}
+          persist-credentials: false
+      - name: "Install Breeze"
+        uses: ./.github/actions/breeze
+      - name: "Make /mnt writeable"
+        run: ./scripts/ci/make_mnt_writeable.sh

Review Comment:
   This job loads a multi-GB image and runs `uv sync` with `--refresh` 
resolutions, but without `free_up_disk_space.sh` / `move_docker_to_mnt.sh` like 
the other image jobs, so Docker stays on `/` and the job can run out of disk 
partway.



##########
.github/workflows/dependency-upgrade-report.yml:
##########
@@ -0,0 +1,167 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+---
+name: Dependency upgrade report
+# Both the job gate and the API validation reject PRs and foreign 
repositories. Only
+# completed main schedule/dispatch runs are executed, with a read-only token 
and no secrets.
+"on":  # zizmor: ignore[dangerous-triggers]
+  workflow_run:
+    workflows: ["Tests (AMD)"]
+    types: [completed]
+    branches: [main]
+permissions:
+  contents: read
+  actions: read
+concurrency:
+  group: dependency-report-${{ github.event.workflow_run.id }}
+  cancel-in-progress: false
+jobs:
+  plan:
+    runs-on: ubuntu-slim
+    timeout-minutes: 5
+    if: >-
+      github.event.workflow_run.head_repository.full_name == github.repository 
&&
+      (github.event.workflow_run.event == 'schedule' ||
+       github.event.workflow_run.event == 'workflow_dispatch')
+    outputs:
+      matrix: ${{ steps.plan.outputs.matrix }}
+      has-reports: ${{ steps.plan.outputs.has-reports }}
+      source-sha: ${{ steps.plan.outputs.source-sha }}
+      source-run-id: ${{ steps.plan.outputs.source-run-id }}
+    steps:
+      - name: "Select inputs from the original canary run"
+        id: plan
+        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3  
# v9.0.0
+        env:
+          SOURCE_RUN_ID: ${{ github.event.workflow_run.id }}
+        with:
+          script: |
+            const rawId = process.env.SOURCE_RUN_ID;
+            if (!/^[1-9][0-9]*$/.test(rawId)) throw new Error('Invalid source 
run ID');
+            const run_id = Number(rawId);
+            if (!Number.isSafeInteger(run_id)) throw new Error('Invalid source 
run ID');
+            const repo = context.repo;
+            const { data: run } = await github.rest.actions.getWorkflowRun({ 
...repo, run_id });
+            if (run.head_repository.full_name !== `${repo.owner}/${repo.repo}` 
||
+                run.head_branch !== 'main' || run.path !== 
'.github/workflows/ci-amd.yml' ||
+                !['schedule', 'workflow_dispatch'].includes(run.event) || 
run.status !== 'completed') {
+              throw new Error('Only completed main AMD canary runs are 
supported');
+            }
+            const artifacts = await 
github.paginate(github.rest.actions.listWorkflowRunArtifacts,
+              { ...repo, run_id, per_page: 100 });
+            const include = [];
+            const inputName = new 
RegExp('^dependency-report-inputs-(3\\.[0-9]+)-' +
+              
'(constraints(?:-source-providers|-no-providers)?)-(true|false)$');
+            for (const input of artifacts) {
+              const match = inputName.exec(input.name);
+              if (!match) continue;
+              const [, python, mode, useUv] = match;
+              const image = artifacts.find(a => a.name === 
`ci-image-save-v3-linux_amd64-${python}-main`);

Review Comment:
   This name comes from the stash action's internal naming scheme. It matches 
what the action produces today (I checked the real artifact), but nothing in 
this repo guarantees it, and only a mocked test uses it.



##########
dev/breeze/doc/ci/05_workflows.md:
##########
@@ -368,6 +368,35 @@ unprivileged, which constrains what can go on it:
 When adding a job, reach for `ubuntu-slim` if it only shuffles metadata 
around, and
 `ubuntu-22.04` otherwise.
 
+### Dependency upgrade diagnostics
+
+Scheduled and manually dispatched AMD canaries on `main` keep the dependency 
freshness
+summary in `finalize-tests.yml`, but run it with `--no-explain-why`. Tests, 
constraint
+generation and validation, and image publication still run in the canary. 
Release-branch
+and other run types retain their existing explanation behavior.
+
+After the canary completes, `dependency-upgrade-report.yml` runs the detailed 
explanations
+independently, including when tests failed but the report inputs were saved. 
It checks out
+the original source SHA and downloads the original image and saved constraints 
by artifact
+ID from that run. It never substitutes the latest branch image or constraints. 
PyPI release
+metadata is fetched when the report executes, so the available upgrade targets 
can change
+between the summary and the explanations.
+
+The workflow has read-only repository permissions and runs only on 
`workflow_run`, which
+cannot write to the default branch's GitHub Actions cache. Reports use the 
saved image artifact.

Review Comment:
   As far as I know, `workflow_run` runs in the default branch's context and 
can write to its cache. What protects the cache here is that no step saves one, 
which is what this should say, so nobody later adds a cache step thinking it is 
safe.



##########
dev/breeze/tests/test_dependency_upgrade_report.py:
##########
@@ -0,0 +1,168 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+import json
+import subprocess
+
+import pytest
+import yaml
+
+from airflow_breeze.utils.path_utils import AIRFLOW_ROOT_PATH
+
+NODE_HARNESS = """
+import { readFileSync } from 'node:fs';
+const input = JSON.parse(readFileSync(0, 'utf8'));
+process.env.SOURCE_RUN_ID = input.id;
+process.env.GITHUB_RUN_ATTEMPT = String(input.attempt);
+const outputs = {};
+const requests = [];
+const summary = {
+  addHeading() { return this; }, addLink() { return this; },
+  addRaw() { return this; }, async write() {}
+};
+const github = {
+  rest: { actions: {
+    async getWorkflowRun(args) { requests.push(args); return { data: input.run 
}; },
+    listWorkflowRunArtifacts() {}
+  } },
+  async paginate(method, args) { requests.push(args); return input.artifacts; }
+};
+const core = { setOutput(key, value) { outputs[key] = value; }, summary, 
notice() {} };
+const context = { repo: { owner: 'apache', repo: 'airflow' } };
+const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
+try {
+  await new AsyncFunction('github', 'context', 'core', input.script)(github, 
context, core);
+  console.log(JSON.stringify({ outputs, requests }));
+} catch (error) {
+  console.log(JSON.stringify({ error: error.message, requests }));
+}
+"""
+
+
[email protected]
+def source_run():
+    return {
+        "head_repository": {"full_name": "apache/airflow"},
+        "head_branch": "main",
+        "path": ".github/workflows/ci-amd.yml",
+        "event": "schedule",
+        "status": "completed",
+        "head_sha": "a" * 40,
+        "html_url": "https://github.com/apache/airflow/actions/runs/123";,
+    }
+
+
[email protected]
+def source_artifacts():
+    return [
+        {
+            "id": 1,
+            "name": 
"dependency-report-inputs-3.14-constraints-source-providers-true",
+            "expired": False,
+        },
+        {"id": 2, "name": "ci-image-save-v3-linux_amd64-3.14-main", "expired": 
False},
+    ]
+
+
+def run_plan(source_run, source_artifacts, *, attempt=1, source_run_id="123"):
+    workflow = yaml.safe_load(
+        (AIRFLOW_ROOT_PATH / 
".github/workflows/dependency-upgrade-report.yml").read_text()
+    )
+    script = workflow["jobs"]["plan"]["steps"][0]["with"]["script"]
+    result = subprocess.run(
+        ["node", "--input-type=module", "--eval", NODE_HARNESS],

Review Comment:
   These tests need `node` on PATH. A `pytest.mark.skipif(shutil.which("node") 
is None, …)` would keep `uv run --project dev/breeze pytest` working for people 
without it.



##########
.github/workflows/finalize-tests.yml:
##########
@@ -149,15 +149,54 @@ jobs:
           python: ${{ matrix.python-version }}
           use-uv: ${{ inputs.use-uv }}
           make-mnt-writeable-and-cleanup: true
+      - name: "Save constraints for the independent dependency report"
+        shell: bash
+        if: >-
+          github.ref == 'refs/heads/main' &&
+          (github.event_name == 'schedule' || github.event_name == 
'workflow_dispatch')
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          CONSTRAINTS_BRANCH: ${{ inputs.constraints-branch }}
+          PYTHON_VERSION: ${{ matrix.python-version }}
+          CONSTRAINTS_MODE: ${{ matrix.constraints-mode }}
+        run: |
+          mkdir -p files/dependency-report
+          REF_API="repos/apache/airflow/git/ref/heads/${CONSTRAINTS_BRANCH}"
+          CONSTRAINTS_SHA=$(gh api "${REF_API}" --jq '.object.sha')
+          
FILE_API="repos/apache/airflow/contents/${CONSTRAINTS_MODE}-${PYTHON_VERSION}.txt"
+          gh api "${FILE_API}?ref=${CONSTRAINTS_SHA}" \
+            --jq '.content' | base64 --decode > 
files/dependency-report/constraints.txt

Review Comment:
   **The canary now depends on raw API calls.** Two `gh api … | base64 
--decode` calls under `pipefail` per Deps job mean a transient API error or 
rate limit turns the canary red. The repo is hard-coded to `apache/airflow` 
(ignoring `CONSTRAINTS_GITHUB_REPOSITORY`), and this duplicates breeze's 
`download_constraints_file`; a `--save-constraints-to` option on the breeze 
command would keep one code path.
   
   Also, these constraints come from the branch tip at summary time, while the 
report checks out the canary's `head_sha`. If `update-constraints-on-push` 
moved the branch in between, version-bound changes show up as false conflicts 
or "stale constraints".



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to