From fb3b1ba6ae8bf407f69c56f7a9a9ae389c1e114a Mon Sep 17 00:00:00 2001 From: isVoid <13521008+isVoid@users.noreply.github.com> Date: Mon, 5 Oct 2026 22:48:07 +0000 Subject: [PATCH 01/13] ci: add manual WoA cross-repository broker --- .github/workflows/woa-crossrepo-smoke.yml | 106 +++++++++++++ .github/workflows/woa-private-dispatch.yml | 172 +++++++++++++++++++++ 2 files changed, 278 insertions(+) create mode 100644 .github/workflows/woa-crossrepo-smoke.yml create mode 100644 .github/workflows/woa-private-dispatch.yml diff --git a/.github/workflows/woa-crossrepo-smoke.yml b/.github/workflows/woa-crossrepo-smoke.yml new file mode 100644 index 00000000000..4f2fd49f913 --- /dev/null +++ b/.github/workflows/woa-crossrepo-smoke.yml @@ -0,0 +1,106 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +name: "WoA cross-repository transport smoke" +run-name: "WoA transport smoke for PR #${{ inputs.pr_number }}" + +on: + workflow_dispatch: + inputs: + pr_number: + description: Open same-repository PR to receive the smoke Check Run + required: true + type: number + +permissions: {} + +jobs: + resolve-public-pr: + if: ${{ github.repository == 'NVIDIA/cuda-python' }} + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + pull-requests: read + outputs: + correlation-id: ${{ steps.resolve.outputs.correlation_id }} + public-sha: ${{ steps.resolve.outputs.public_sha }} + steps: + - name: Resolve exact same-repository PR head + id: resolve + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ inputs.pr_number }} + run: | + set -euo pipefail + [[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]] + pr=$(gh api \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA/cuda-python/pulls/$PR_NUMBER") + jq -e ' + .state == "open" and + .base.ref == "main" and + .head.repo.full_name == "NVIDIA/cuda-python" and + (.head.sha | test("^[0-9a-f]{40}$")) + ' <<< "$pr" >/dev/null + public_sha=$(jq -r .head.sha <<< "$pr") + correlation_id="smoke:381173759:$PR_NUMBER:$public_sha:${GITHUB_RUN_ID}:${GITHUB_RUN_ATTEMPT}" + echo "public_sha=$public_sha" >> "$GITHUB_OUTPUT" + echo "correlation_id=$correlation_id" >> "$GITHUB_OUTPUT" + + dispatch-private-smoke: + needs: resolve-public-pr + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: {} + steps: + - name: Create private dispatch token + id: private-app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 + with: + client-id: ${{ vars.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_CLIENT_ID }} + private-key: ${{ secrets.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_PRIVATE_KEY }} + owner: NVIDIA-dev + repositories: cuda-python-private + permission-actions: write + + - name: Dispatch and verify private smoke workflow + env: + CORRELATION_ID: ${{ needs.resolve-public-pr.outputs.correlation-id }} + GH_TOKEN: ${{ steps.private-app-token.outputs.token }} + PR_NUMBER: ${{ inputs.pr_number }} + PUBLIC_SHA: ${{ needs.resolve-public-pr.outputs.public-sha }} + run: | + set -euo pipefail + payload=$(jq -n \ + --arg correlation_id "$CORRELATION_ID" \ + --arg pr_number "$PR_NUMBER" \ + --arg public_sha "$PUBLIC_SHA" \ + '{ + ref: "ctk-next", + return_run_details: true, + inputs: { + schema_version: "1", + public_repository: "NVIDIA/cuda-python", + public_repository_id: "381173759", + public_pr_number: $pr_number, + public_sha: $public_sha, + correlation_id: $correlation_id + } + }') + response=$(gh api \ + --method POST \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + repos/NVIDIA-dev/cuda-python-private/actions/workflows/woa-crossrepo-smoke.yml/dispatches \ + --input - <<< "$payload") + private_run_id=$(jq -er '.workflow_run_id | tostring' <<< "$response") + run=$(gh api \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA-dev/cuda-python-private/actions/runs/$private_run_id") + jq -e ' + .repository.id == 809898190 and + .path == ".github/workflows/woa-crossrepo-smoke.yml" and + .event == "workflow_dispatch" and + .head_branch == "ctk-next" + ' <<< "$run" >/dev/null + echo "Private transport-smoke workflow accepted the exact dispatch." >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/woa-private-dispatch.yml b/.github/workflows/woa-private-dispatch.yml new file mode 100644 index 00000000000..0a9d234545e --- /dev/null +++ b/.github/workflows/woa-private-dispatch.yml @@ -0,0 +1,172 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +name: "WoA exact public-main dispatch" +run-name: "WoA exact dispatch for public run ${{ inputs.public_run_id }}" + +on: + workflow_dispatch: + inputs: + public_run_id: + description: Successful public main CI run containing the exact WoA artifacts + required: true + type: string + +permissions: {} + +jobs: + resolve-public-build: + if: ${{ github.repository == 'NVIDIA/cuda-python' }} + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + actions: read + contents: read + checks: read + outputs: + artifacts-json: ${{ steps.resolve.outputs.artifacts_json }} + correlation-id: ${{ steps.resolve.outputs.correlation_id }} + public-run-attempt: ${{ steps.resolve.outputs.public_run_attempt }} + public-sha: ${{ steps.resolve.outputs.public_sha }} + steps: + - name: Resolve exact successful public build + id: resolve + env: + GH_TOKEN: ${{ github.token }} + PUBLIC_RUN_ID: ${{ inputs.public_run_id }} + run: | + set -euo pipefail + [[ "$PUBLIC_RUN_ID" =~ ^[1-9][0-9]*$ ]] + run=$(gh api \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA/cuda-python/actions/runs/$PUBLIC_RUN_ID") + jq -e ' + .repository.id == 381173759 and + .workflow_id == 155304118 and + .path == ".github/workflows/ci.yml" and + .event == "push" and + .head_branch == "main" and + .status == "completed" and + .conclusion == "success" and + (.head_sha | test("^[0-9a-f]{40}$")) + ' <<< "$run" >/dev/null + public_sha=$(jq -r .head_sha <<< "$run") + public_run_attempt=$(jq -r '.run_attempt | tostring' <<< "$run") + + comparison=$(gh api \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA/cuda-python/compare/$public_sha...main") + jq -e '.status == "ahead" or .status == "identical"' <<< "$comparison" >/dev/null + + jobs=$(gh api --paginate \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA/cuda-python/actions/runs/$PUBLIC_RUN_ID/attempts/$public_run_attempt/jobs?per_page=100" \ + --jq '.jobs') + jq -s -e ' + add + | map(select(.name | startswith("Build win-arm64, CUDA "))) + | length > 0 and all(.[]; .status == "completed" and .conclusion == "success") + ' <<< "$jobs" >/dev/null + + artifacts=$(gh api --paginate \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA/cuda-python/actions/runs/$PUBLIC_RUN_ID/artifacts?per_page=100" \ + --jq '.artifacts' | jq -s 'add') + artifacts_json=$(jq -c \ + --arg sha "$public_sha" ' + map(select( + .expired == false and + ( + .name == "cuda-pathfinder-wheel" or + (.name | test("^cuda-bindings-python313-cuda[0-9.]+-win-arm64-" + $sha + "$")) or + .name == ("cuda-core-python313-win-arm64-" + $sha) + ) + )) + | map({id, name, digest}) + | sort_by(.name) + ' <<< "$artifacts") + jq -e ' + length == 3 and + all(.[ ]; + (.id | type == "number") and + (.digest | test("^sha256:[0-9a-f]{64}$"))) + ' <<< "$artifacts_json" >/dev/null + + correlation_id="v1:381173759:$PUBLIC_RUN_ID:$public_run_attempt:$public_sha" + echo "artifacts_json=$artifacts_json" >> "$GITHUB_OUTPUT" + echo "correlation_id=$correlation_id" >> "$GITHUB_OUTPUT" + echo "public_run_attempt=$public_run_attempt" >> "$GITHUB_OUTPUT" + echo "public_sha=$public_sha" >> "$GITHUB_OUTPUT" + { + echo "Resolved public run \`$PUBLIC_RUN_ID\` attempt \`$public_run_attempt\`." + echo "" + echo "Exact Windows Arm64 artifacts:" + jq -r '.[] | "- `\(.name)` (`\(.digest)`)"' <<< "$artifacts_json" + } >> "$GITHUB_STEP_SUMMARY" + + dispatch-private-validation: + needs: resolve-public-build + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: {} + steps: + - name: Create private dispatch token + id: private-app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 + with: + client-id: ${{ vars.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_CLIENT_ID }} + private-key: ${{ secrets.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_PRIVATE_KEY }} + owner: NVIDIA-dev + repositories: cuda-python-private + permission-actions: write + + - name: Dispatch and verify exact private validation + env: + ARTIFACTS_JSON: ${{ needs.resolve-public-build.outputs.artifacts-json }} + CORRELATION_ID: ${{ needs.resolve-public-build.outputs.correlation-id }} + GH_TOKEN: ${{ steps.private-app-token.outputs.token }} + PUBLIC_RUN_ATTEMPT: ${{ needs.resolve-public-build.outputs.public-run-attempt }} + PUBLIC_RUN_ID: ${{ inputs.public_run_id }} + PUBLIC_SHA: ${{ needs.resolve-public-build.outputs.public-sha }} + run: | + set -euo pipefail + payload=$(jq -n \ + --arg artifacts_json "$ARTIFACTS_JSON" \ + --arg correlation_id "$CORRELATION_ID" \ + --arg run_attempt "$PUBLIC_RUN_ATTEMPT" \ + --arg run_id "$PUBLIC_RUN_ID" \ + --arg sha "$PUBLIC_SHA" \ + '{ + ref: "ctk-next", + return_run_details: true, + inputs: { + schema_version: "1", + public_repository: "NVIDIA/cuda-python", + public_repository_id: "381173759", + public_workflow_id: "155304118", + public_run_id: $run_id, + public_run_attempt: $run_attempt, + public_sha: $sha, + artifacts_json: $artifacts_json, + baseline_sha: "", + commit_count: "1", + correlation_id: $correlation_id + } + }') + response=$(gh api \ + --method POST \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + repos/NVIDIA-dev/cuda-python-private/actions/workflows/woa-main-validation.yml/dispatches \ + --input - <<< "$payload") + private_run_id=$(jq -er '.workflow_run_id | tostring' <<< "$response") + run=$(gh api \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA-dev/cuda-python-private/actions/runs/$private_run_id") + jq -e ' + .repository.id == 809898190 and + .path == ".github/workflows/woa-main-validation.yml" and + .event == "workflow_dispatch" and + .head_branch == "ctk-next" + ' <<< "$run" >/dev/null + echo "Private validation workflow accepted the exact dispatch." >> "$GITHUB_STEP_SUMMARY" From e64deb49d5ce127926149a55e2f8538c8df74d01 Mon Sep 17 00:00:00 2001 From: isVoid <13521008+isVoid@users.noreply.github.com> Date: Mon, 5 Oct 2026 22:58:47 +0000 Subject: [PATCH 02/13] ci: automate WoA cross-repository dispatch --- .github/workflows/woa-main-automation.yml | 257 +++++++++++++++++++ ci/tools/woa_xrepo_select.py | 292 ++++++++++++++++++++++ 2 files changed, 549 insertions(+) create mode 100644 .github/workflows/woa-main-automation.yml create mode 100644 ci/tools/woa_xrepo_select.py diff --git a/.github/workflows/woa-main-automation.yml b/.github/workflows/woa-main-automation.yml new file mode 100644 index 00000000000..c5b1c0287c6 --- /dev/null +++ b/.github/workflows/woa-main-automation.yml @@ -0,0 +1,257 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +name: "WoA public-main automation" + +on: + workflow_run: + workflows: + - CI + types: + - completed + schedule: + - cron: "17 * * * *" + workflow_dispatch: + inputs: + public_run_id: + description: Optional exact public CI run; empty selects the newest eligible build + required: false + type: string + +permissions: {} + +jobs: + select: + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion != 'cancelled' }} + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + actions: read + checks: read + contents: read + concurrency: + group: cuda-python-woa-public-selector + cancel-in-progress: false + outputs: + artifacts-json: ${{ steps.select.outputs.artifacts_json }} + baseline-sha: ${{ steps.select.outputs.baseline_sha }} + commit-count: ${{ steps.select.outputs.commit_count }} + correlation-id: ${{ steps.select.outputs.correlation_id }} + dispatch: ${{ steps.select.outputs.dispatch }} + public-run-attempt: ${{ steps.select.outputs.public_run_attempt }} + public-run-id: ${{ steps.select.outputs.public_run_id }} + public-sha: ${{ steps.select.outputs.public_sha }} + steps: + - name: Checkout trusted selector + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 1 + persist-credentials: false + ref: main + + - name: Select exact public batch + id: select + env: + EVENT_CANDIDATE_RUN_ID: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.id || '' }} + GITHUB_TOKEN: ${{ github.token }} + MANUAL_CANDIDATE_RUN_ID: ${{ inputs.public_run_id }} + run: | + set -euo pipefail + candidate_run_id="$EVENT_CANDIDATE_RUN_ID" + if [[ -n "$MANUAL_CANDIDATE_RUN_ID" ]]; then + candidate_run_id="$MANUAL_CANDIDATE_RUN_ID" + fi + selection=$(python ci/tools/woa_xrepo_select.py --run-id "$candidate_run_id") + jq . <<< "$selection" + echo "dispatch=$(jq -r .dispatch <<< "$selection")" >> "$GITHUB_OUTPUT" + echo "artifacts_json=$(jq -c '.artifacts // []' <<< "$selection")" >> "$GITHUB_OUTPUT" + echo "baseline_sha=$(jq -r '.baseline_sha // ""' <<< "$selection")" >> "$GITHUB_OUTPUT" + echo "commit_count=$(jq -r '.commit_count // 0' <<< "$selection")" >> "$GITHUB_OUTPUT" + echo "correlation_id=$(jq -r '.correlation_id // ""' <<< "$selection")" >> "$GITHUB_OUTPUT" + echo "public_run_attempt=$(jq -r '.run_attempt // ""' <<< "$selection")" >> "$GITHUB_OUTPUT" + echo "public_run_id=$(jq -r '.run_id // ""' <<< "$selection")" >> "$GITHUB_OUTPUT" + echo "public_sha=$(jq -r '.sha // ""' <<< "$selection")" >> "$GITHUB_OUTPUT" + echo "$(jq -r .reason <<< "$selection")" >> "$GITHUB_STEP_SUMMARY" + + dispatch: + needs: select + if: ${{ needs.select.outputs.dispatch == 'true' }} + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: {} + steps: + - name: Create private dispatch token + id: private-app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 + with: + client-id: ${{ vars.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_CLIENT_ID }} + private-key: ${{ secrets.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_PRIVATE_KEY }} + owner: NVIDIA-dev + repositories: cuda-python-private + permission-actions: write + + - name: Dispatch exact private validation + env: + ARTIFACTS_JSON: ${{ needs.select.outputs.artifacts-json }} + BASELINE_SHA: ${{ needs.select.outputs.baseline-sha }} + COMMIT_COUNT: ${{ needs.select.outputs.commit-count }} + CORRELATION_ID: ${{ needs.select.outputs.correlation-id }} + GH_TOKEN: ${{ steps.private-app-token.outputs.token }} + PUBLIC_RUN_ATTEMPT: ${{ needs.select.outputs.public-run-attempt }} + PUBLIC_RUN_ID: ${{ needs.select.outputs.public-run-id }} + PUBLIC_SHA: ${{ needs.select.outputs.public-sha }} + run: | + set -euo pipefail + payload=$(jq -n \ + --arg artifacts_json "$ARTIFACTS_JSON" \ + --arg baseline_sha "$BASELINE_SHA" \ + --arg commit_count "$COMMIT_COUNT" \ + --arg correlation_id "$CORRELATION_ID" \ + --arg run_attempt "$PUBLIC_RUN_ATTEMPT" \ + --arg run_id "$PUBLIC_RUN_ID" \ + --arg sha "$PUBLIC_SHA" \ + '{ + ref: "ctk-next", + return_run_details: true, + inputs: { + schema_version: "1", + public_repository: "NVIDIA/cuda-python", + public_repository_id: "381173759", + public_workflow_id: "155304118", + public_run_id: $run_id, + public_run_attempt: $run_attempt, + public_sha: $sha, + artifacts_json: $artifacts_json, + baseline_sha: $baseline_sha, + commit_count: $commit_count, + correlation_id: $correlation_id + } + }') + response=$(gh api \ + --method POST \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + repos/NVIDIA-dev/cuda-python-private/actions/workflows/woa-main-validation.yml/dispatches \ + --input - <<< "$payload") + private_run_id=$(jq -er '.workflow_run_id | tostring' <<< "$response") + private_run=$(gh api \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA-dev/cuda-python-private/actions/runs/$private_run_id") + jq -e \ + --arg title "WoA validation $CORRELATION_ID" ' + .repository.id == 809898190 and + .path == ".github/workflows/woa-main-validation.yml" and + .event == "workflow_dispatch" and + .head_branch == "ctk-next" and + .display_title == $title + ' <<< "$private_run" >/dev/null + echo "Exact private validation accepted the selected public batch." >> "$GITHUB_STEP_SUMMARY" + + validate-cancellation: + if: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'cancelled' }} + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + actions: read + outputs: + correlation-id: ${{ steps.validate.outputs.correlation_id }} + public-run-attempt: ${{ steps.validate.outputs.public_run_attempt }} + public-run-id: ${{ steps.validate.outputs.public_run_id }} + public-sha: ${{ steps.validate.outputs.public_sha }} + steps: + - name: Validate exact cancelled public run + id: validate + env: + GH_TOKEN: ${{ github.token }} + PUBLIC_RUN_ID: ${{ github.event.workflow_run.id }} + run: | + set -euo pipefail + run=$(gh api \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA/cuda-python/actions/runs/$PUBLIC_RUN_ID") + jq -e ' + .repository.id == 381173759 and + .workflow_id == 155304118 and + .path == ".github/workflows/ci.yml" and + .event == "push" and + .head_branch == "main" and + .status == "completed" and + .conclusion == "cancelled" + ' <<< "$run" >/dev/null + public_sha=$(jq -r .head_sha <<< "$run") + public_run_attempt=$(jq -r '.run_attempt | tostring' <<< "$run") + correlation_id="v1:381173759:$PUBLIC_RUN_ID:$public_run_attempt:$public_sha" + echo "public_run_id=$PUBLIC_RUN_ID" >> "$GITHUB_OUTPUT" + echo "public_run_attempt=$public_run_attempt" >> "$GITHUB_OUTPUT" + echo "public_sha=$public_sha" >> "$GITHUB_OUTPUT" + echo "correlation_id=$correlation_id" >> "$GITHUB_OUTPUT" + + dispatch-cancellation: + needs: validate-cancellation + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: {} + steps: + - name: Create private dispatch token + id: private-app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 + with: + client-id: ${{ vars.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_CLIENT_ID }} + private-key: ${{ secrets.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_PRIVATE_KEY }} + owner: NVIDIA-dev + repositories: cuda-python-private + permission-actions: write + + - name: Locate exact private validation and dispatch cancellation + env: + CORRELATION_ID: ${{ needs.validate-cancellation.outputs.correlation-id }} + GH_TOKEN: ${{ steps.private-app-token.outputs.token }} + PUBLIC_RUN_ATTEMPT: ${{ needs.validate-cancellation.outputs.public-run-attempt }} + PUBLIC_RUN_ID: ${{ needs.validate-cancellation.outputs.public-run-id }} + PUBLIC_SHA: ${{ needs.validate-cancellation.outputs.public-sha }} + run: | + set -euo pipefail + runs=$(gh api \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + 'repos/NVIDIA-dev/cuda-python-private/actions/workflows/woa-main-validation.yml/runs?event=workflow_dispatch&per_page=100') + matches=$(jq -c \ + --arg title "WoA validation $CORRELATION_ID" ' + [.workflow_runs[] | select(.display_title == $title)] + ' <<< "$runs") + match_count=$(jq length <<< "$matches") + if (( match_count == 0 )); then + echo "No correlated private validation exists; cancellation is a no-op." >> "$GITHUB_STEP_SUMMARY" + exit 0 + fi + if (( match_count != 1 )); then + echo "Expected one correlated private validation, found $match_count." >&2 + exit 1 + fi + private_run_id=$(jq -r '.[0].id | tostring' <<< "$matches") + + payload=$(jq -n \ + --arg correlation_id "$CORRELATION_ID" \ + --arg private_run_id "$private_run_id" \ + --arg run_attempt "$PUBLIC_RUN_ATTEMPT" \ + --arg run_id "$PUBLIC_RUN_ID" \ + --arg sha "$PUBLIC_SHA" ' + { + ref: "ctk-next", + return_run_details: true, + inputs: { + schema_version: "1", + public_repository_id: "381173759", + public_run_id: $run_id, + public_run_attempt: $run_attempt, + public_sha: $sha, + private_run_id: $private_run_id, + correlation_id: $correlation_id + } + }') + response=$(gh api \ + --method POST \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + repos/NVIDIA-dev/cuda-python-private/actions/workflows/woa-main-cancel.yml/dispatches \ + --input - <<< "$payload") + jq -e '.workflow_run_id | type == "number"' <<< "$response" >/dev/null + echo "Private cancellation controller accepted the exact request." >> "$GITHUB_STEP_SUMMARY" diff --git a/ci/tools/woa_xrepo_select.py b/ci/tools/woa_xrepo_select.py new file mode 100644 index 00000000000..6489fe47104 --- /dev/null +++ b/ci/tools/woa_xrepo_select.py @@ -0,0 +1,292 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +import argparse +import datetime +import json +import os +import re +import sys +import urllib.error +import urllib.parse +import urllib.request + + +PUBLIC_REPOSITORY = "NVIDIA/cuda-python" +PUBLIC_REPOSITORY_ID = 381173759 +PUBLIC_WORKFLOW_ID = 155304118 +PUBLIC_WORKFLOW_PATH = ".github/workflows/ci.yml" +REPORTING_APP_ID = 4954254 +CHECK_NAME = "cuda-python WoA integration" +MAX_LEDGER_COMMITS = 100 + + +class GitHubAPI: + def __init__(self, token): + self._token = token + + def get(self, path, params=None): + url = f"https://api.github.com/{path.lstrip('/')}" + if params: + url = f"{url}?{urllib.parse.urlencode(params)}" + request = urllib.request.Request( + url, + headers={ + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {self._token}", + "X-GitHub-Api-Version": "2026-03-10", + }, + ) + try: + with urllib.request.urlopen(request) as response: + return json.load(response) + except urllib.error.HTTPError as error: + body = error.read().decode("utf-8", errors="replace") + raise RuntimeError(f"GitHub API GET {path} failed: {error.code}: {body}") from error + + def paginate(self, path, item_key, params=None, max_pages=10): + items = [] + query = dict(params or {}) + query["per_page"] = 100 + for page in range(1, max_pages + 1): + query["page"] = page + response = self.get(path, query) + page_items = response[item_key] + items.extend(page_items) + if len(page_items) < 100: + break + return items + + +def parse_time(value): + return datetime.datetime.fromisoformat(value.replace("Z", "+00:00")) + + +def select_artifacts(artifacts, sha): + binding_name = f"cuda-bindings-python313-cuda13.4.2-win-arm64-{sha}" + core_name = f"cuda-core-python313-win-arm64-{sha}" + selected = [ + artifact + for artifact in artifacts + if not artifact["expired"] + and ( + artifact["name"] == "cuda-pathfinder-wheel" + or artifact["name"] == binding_name + or artifact["name"] == core_name + ) + ] + if len(selected) != 3: + return None + names = {artifact["name"] for artifact in selected} + if len(names) != 3: + return None + result = [] + for artifact in sorted(selected, key=lambda item: item["name"]): + digest = artifact.get("digest") + if not isinstance(digest, str) or not re.fullmatch(r"sha256:[0-9a-f]{64}", digest): + return None + result.append( + {"id": artifact["id"], "name": artifact["name"], "digest": digest} + ) + return result + + +def validate_run(api, run): + if not ( + run["repository"]["id"] == PUBLIC_REPOSITORY_ID + and run["workflow_id"] == PUBLIC_WORKFLOW_ID + and run["path"] == PUBLIC_WORKFLOW_PATH + and run["event"] == "push" + and run["head_branch"] == "main" + and run["status"] == "completed" + and run["conclusion"] == "success" + and re.fullmatch(r"[0-9a-f]{40}", run["head_sha"]) + ): + return None + + run_id = run["id"] + attempt = run["run_attempt"] + jobs = api.paginate( + f"repos/{PUBLIC_REPOSITORY}/actions/runs/{run_id}/attempts/{attempt}/jobs", + "jobs", + ) + woa_jobs = [job for job in jobs if job["name"].startswith("Build win-arm64, CUDA ")] + if not woa_jobs or any( + job["status"] != "completed" or job["conclusion"] != "success" for job in woa_jobs + ): + return None + + artifacts = api.paginate( + f"repos/{PUBLIC_REPOSITORY}/actions/runs/{run_id}/artifacts", "artifacts" + ) + selected_artifacts = select_artifacts(artifacts, run["head_sha"]) + if selected_artifacts is None: + return None + + comparison = api.get( + f"repos/{PUBLIC_REPOSITORY}/compare/{run['head_sha']}...main" + ) + if comparison["status"] not in {"ahead", "identical"}: + return None + + return { + "run_id": str(run_id), + "run_attempt": str(attempt), + "sha": run["head_sha"], + "artifacts": selected_artifacts, + } + + +def resolve_candidate(api, run_id): + if run_id: + run = api.get(f"repos/{PUBLIC_REPOSITORY}/actions/runs/{run_id}") + return validate_run(api, run) + + runs = api.get( + f"repos/{PUBLIC_REPOSITORY}/actions/workflows/ci.yml/runs", + { + "branch": "main", + "event": "push", + "status": "completed", + "per_page": 30, + }, + )["workflow_runs"] + for run in runs: + candidate = validate_run(api, run) + if candidate is not None: + return candidate + return None + + +def app_checks(api, sha): + response = api.get( + f"repos/{PUBLIC_REPOSITORY}/commits/{sha}/check-runs", + {"check_name": CHECK_NAME, "per_page": 100}, + ) + return [ + check + for check in response["check_runs"] + if check["name"] == CHECK_NAME and check["app"]["id"] == REPORTING_APP_ID + ] + + +def list_commits(api): + return api.get( + f"repos/{PUBLIC_REPOSITORY}/commits", + {"sha": "main", "per_page": MAX_LEDGER_COMMITS}, + ) + + +def select_batch(api, candidate, batch_size, max_wait_seconds, now): + candidate_checks = app_checks(api, candidate["sha"]) + external_id = ( + f"woa:v1:{PUBLIC_REPOSITORY_ID}:{candidate['run_id']}:" + f"{candidate['run_attempt']}:{candidate['sha']}" + ) + if any( + check.get("external_id") == external_id + for check in candidate_checks + ): + return {"dispatch": False, "reason": "candidate already has an App-owned attempt"} + + commits = list_commits(api) + try: + candidate_index = next( + index for index, commit in enumerate(commits) if commit["sha"] == candidate["sha"] + ) + except StopIteration as error: + raise RuntimeError( + f"candidate SHA was not found on the latest {MAX_LEDGER_COMMITS} main commits" + ) from error + + baseline_index = None + baseline_sha = "" + for index in range(candidate_index + 1, len(commits)): + checks = app_checks(api, commits[index]["sha"]) + successful = [ + check + for check in checks + if check["status"] == "completed" and check["conclusion"] == "success" + ] + if successful: + baseline_index = index + baseline_sha = commits[index]["sha"] + break + + if baseline_index is None: + return { + "dispatch": True, + "reason": ( + "bootstrap: no successful App-owned baseline exists in the bounded ledger" + ), + "baseline_sha": "", + "commit_count": 1, + } + + commit_count = baseline_index - candidate_index + oldest_pending = commits[baseline_index - 1] + oldest_time = parse_time(oldest_pending["commit"]["committer"]["date"]) + age_seconds = max(0, int((now - oldest_time).total_seconds())) + if commit_count >= batch_size: + reason = f"batch threshold reached: {commit_count} commits" + dispatch = True + elif age_seconds >= max_wait_seconds: + reason = f"maximum wait reached: oldest pending commit is {age_seconds} seconds old" + dispatch = True + else: + reason = ( + f"deferred: {commit_count}/{batch_size} commits and oldest pending age " + f"{age_seconds}/{max_wait_seconds} seconds" + ) + dispatch = False + return { + "dispatch": dispatch, + "reason": reason, + "baseline_sha": baseline_sha, + "commit_count": commit_count, + } + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--run-id", default="") + parser.add_argument("--batch-size", type=int, default=3) + parser.add_argument("--max-wait-seconds", type=int, default=7200) + args = parser.parse_args() + if args.run_id and not args.run_id.isdigit(): + parser.error("--run-id must be numeric") + if args.batch_size < 1 or args.max_wait_seconds < 1: + parser.error("batch size and maximum wait must be positive") + + token = os.environ.get("GITHUB_TOKEN") + if not token: + raise SystemExit("GITHUB_TOKEN is required") + api = GitHubAPI(token) + candidate = resolve_candidate(api, args.run_id) + if candidate is None: + print(json.dumps({"dispatch": False, "reason": "no eligible public WoA build"})) + return + + selection = select_batch( + api, + candidate, + args.batch_size, + args.max_wait_seconds, + datetime.datetime.now(datetime.timezone.utc), + ) + selection.update(candidate) + selection["correlation_id"] = ( + f"v1:{PUBLIC_REPOSITORY_ID}:{candidate['run_id']}:" + f"{candidate['run_attempt']}:{candidate['sha']}" + ) + print(json.dumps(selection, separators=(",", ":"), sort_keys=True)) + + +if __name__ == "__main__": + try: + main() + except Exception as error: + print(f"error: {error}", file=sys.stderr) + raise From bf3af4ed248d1f287e52bf3a83b26e96bef2a7dc Mon Sep 17 00:00:00 2001 From: isVoid <13521008+isVoid@users.noreply.github.com> Date: Mon, 5 Oct 2026 23:03:28 +0000 Subject: [PATCH 03/13] ci: satisfy selector lint policy --- ci/tools/woa_xrepo_select.py | 88 ++++++++++++++---------------------- 1 file changed, 33 insertions(+), 55 deletions(-) diff --git a/ci/tools/woa_xrepo_select.py b/ci/tools/woa_xrepo_select.py index 6489fe47104..4fd9740cf9b 100644 --- a/ci/tools/woa_xrepo_select.py +++ b/ci/tools/woa_xrepo_select.py @@ -5,14 +5,12 @@ import argparse import datetime +import http.client import json import os import re import sys -import urllib.error import urllib.parse -import urllib.request - PUBLIC_REPOSITORY = "NVIDIA/cuda-python" PUBLIC_REPOSITORY_ID = 381173759 @@ -28,23 +26,28 @@ def __init__(self, token): self._token = token def get(self, path, params=None): - url = f"https://api.github.com/{path.lstrip('/')}" + request_path = f"/{path.lstrip('/')}" if params: - url = f"{url}?{urllib.parse.urlencode(params)}" - request = urllib.request.Request( - url, - headers={ - "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {self._token}", - "X-GitHub-Api-Version": "2026-03-10", - }, - ) + request_path = f"{request_path}?{urllib.parse.urlencode(params)}" + connection = http.client.HTTPSConnection("api.github.com") try: - with urllib.request.urlopen(request) as response: - return json.load(response) - except urllib.error.HTTPError as error: - body = error.read().decode("utf-8", errors="replace") - raise RuntimeError(f"GitHub API GET {path} failed: {error.code}: {body}") from error + connection.request( + "GET", + request_path, + headers={ + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {self._token}", + "X-GitHub-Api-Version": "2026-03-10", + "User-Agent": "cuda-python-woa-selector", + }, + ) + response = connection.getresponse() + body = response.read().decode("utf-8", errors="replace") + if response.status >= 400: + raise RuntimeError(f"GitHub API GET {path} failed: {response.status}: {body}") + return json.loads(body) + finally: + connection.close() def paginate(self, path, item_key, params=None, max_pages=10): items = [] @@ -73,7 +76,7 @@ def select_artifacts(artifacts, sha): if not artifact["expired"] and ( artifact["name"] == "cuda-pathfinder-wheel" - or artifact["name"] == binding_name + or artifact["name"] == binding_name or artifact["name"] == core_name ) ] @@ -87,9 +90,7 @@ def select_artifacts(artifacts, sha): digest = artifact.get("digest") if not isinstance(digest, str) or not re.fullmatch(r"sha256:[0-9a-f]{64}", digest): return None - result.append( - {"id": artifact["id"], "name": artifact["name"], "digest": digest} - ) + result.append({"id": artifact["id"], "name": artifact["name"], "digest": digest}) return result @@ -113,21 +114,15 @@ def validate_run(api, run): "jobs", ) woa_jobs = [job for job in jobs if job["name"].startswith("Build win-arm64, CUDA ")] - if not woa_jobs or any( - job["status"] != "completed" or job["conclusion"] != "success" for job in woa_jobs - ): + if not woa_jobs or any(job["status"] != "completed" or job["conclusion"] != "success" for job in woa_jobs): return None - artifacts = api.paginate( - f"repos/{PUBLIC_REPOSITORY}/actions/runs/{run_id}/artifacts", "artifacts" - ) + artifacts = api.paginate(f"repos/{PUBLIC_REPOSITORY}/actions/runs/{run_id}/artifacts", "artifacts") selected_artifacts = select_artifacts(artifacts, run["head_sha"]) if selected_artifacts is None: return None - comparison = api.get( - f"repos/{PUBLIC_REPOSITORY}/compare/{run['head_sha']}...main" - ) + comparison = api.get(f"repos/{PUBLIC_REPOSITORY}/compare/{run['head_sha']}...main") if comparison["status"] not in {"ahead", "identical"}: return None @@ -181,35 +176,21 @@ def list_commits(api): def select_batch(api, candidate, batch_size, max_wait_seconds, now): candidate_checks = app_checks(api, candidate["sha"]) - external_id = ( - f"woa:v1:{PUBLIC_REPOSITORY_ID}:{candidate['run_id']}:" - f"{candidate['run_attempt']}:{candidate['sha']}" - ) - if any( - check.get("external_id") == external_id - for check in candidate_checks - ): + external_id = f"woa:v1:{PUBLIC_REPOSITORY_ID}:{candidate['run_id']}:{candidate['run_attempt']}:{candidate['sha']}" + if any(check.get("external_id") == external_id for check in candidate_checks): return {"dispatch": False, "reason": "candidate already has an App-owned attempt"} commits = list_commits(api) try: - candidate_index = next( - index for index, commit in enumerate(commits) if commit["sha"] == candidate["sha"] - ) + candidate_index = next(index for index, commit in enumerate(commits) if commit["sha"] == candidate["sha"]) except StopIteration as error: - raise RuntimeError( - f"candidate SHA was not found on the latest {MAX_LEDGER_COMMITS} main commits" - ) from error + raise RuntimeError(f"candidate SHA was not found on the latest {MAX_LEDGER_COMMITS} main commits") from error baseline_index = None baseline_sha = "" for index in range(candidate_index + 1, len(commits)): checks = app_checks(api, commits[index]["sha"]) - successful = [ - check - for check in checks - if check["status"] == "completed" and check["conclusion"] == "success" - ] + successful = [check for check in checks if check["status"] == "completed" and check["conclusion"] == "success"] if successful: baseline_index = index baseline_sha = commits[index]["sha"] @@ -218,9 +199,7 @@ def select_batch(api, candidate, batch_size, max_wait_seconds, now): if baseline_index is None: return { "dispatch": True, - "reason": ( - "bootstrap: no successful App-owned baseline exists in the bounded ledger" - ), + "reason": ("bootstrap: no successful App-owned baseline exists in the bounded ledger"), "baseline_sha": "", "commit_count": 1, } @@ -278,8 +257,7 @@ def main(): ) selection.update(candidate) selection["correlation_id"] = ( - f"v1:{PUBLIC_REPOSITORY_ID}:{candidate['run_id']}:" - f"{candidate['run_attempt']}:{candidate['sha']}" + f"v1:{PUBLIC_REPOSITORY_ID}:{candidate['run_id']}:{candidate['run_attempt']}:{candidate['sha']}" ) print(json.dumps(selection, separators=(",", ":"), sort_keys=True)) From eb4adeaee6abcfc4e80e39925dfc471903bcd64b Mon Sep 17 00:00:00 2001 From: isVoid <13521008+isVoid@users.noreply.github.com> Date: Tue, 6 Oct 2026 06:26:19 +0000 Subject: [PATCH 04/13] ci: dispatch exact WoA producer identity --- .github/workflows/woa-private-dispatch.yml | 64 ++++++++++++++-------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/.github/workflows/woa-private-dispatch.yml b/.github/workflows/woa-private-dispatch.yml index 0a9d234545e..e01fd107243 100644 --- a/.github/workflows/woa-private-dispatch.yml +++ b/.github/workflows/woa-private-dispatch.yml @@ -27,6 +27,7 @@ jobs: outputs: artifacts-json: ${{ steps.resolve.outputs.artifacts_json }} correlation-id: ${{ steps.resolve.outputs.correlation_id }} + public-producer-job-id: ${{ steps.resolve.outputs.public_producer_job_id }} public-run-attempt: ${{ steps.resolve.outputs.public_run_attempt }} public-sha: ${{ steps.resolve.outputs.public_sha }} steps: @@ -63,29 +64,44 @@ jobs: -H 'X-GitHub-Api-Version: 2026-03-10' \ "repos/NVIDIA/cuda-python/actions/runs/$PUBLIC_RUN_ID/attempts/$public_run_attempt/jobs?per_page=100" \ --jq '.jobs') - jq -s -e ' + producer_jobs=$(jq -s -c ' add - | map(select(.name | startswith("Build win-arm64, CUDA "))) - | length > 0 and all(.[]; .status == "completed" and .conclusion == "success") - ' <<< "$jobs" >/dev/null + | map(select(.name == "Build win-arm64, CUDA 13.4.2 / py3.13")) + ' <<< "$jobs") + jq -e ' + length == 1 and + .[0].status == "completed" and + .[0].conclusion == "success" + ' <<< "$producer_jobs" >/dev/null + public_producer_job_id=$(jq -r '.[0].id | tostring' <<< "$producer_jobs") - artifacts=$(gh api --paginate \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - "repos/NVIDIA/cuda-python/actions/runs/$PUBLIC_RUN_ID/artifacts?per_page=100" \ - --jq '.artifacts' | jq -s 'add') - artifacts_json=$(jq -c \ - --arg sha "$public_sha" ' - map(select( - .expired == false and - ( - .name == "cuda-pathfinder-wheel" or - (.name | test("^cuda-bindings-python313-cuda[0-9.]+-win-arm64-" + $sha + "$")) or - .name == ("cuda-core-python313-win-arm64-" + $sha) - ) - )) - | map({id, name, digest}) - | sort_by(.name) - ' <<< "$artifacts") + expected_names=$(jq -n \ + --arg sha "$public_sha" '[ + "cuda-pathfinder-wheel", + ("cuda-bindings-python313-cuda13.4.2-win-arm64-" + $sha), + ("cuda-core-python313-win-arm64-" + $sha) + ]') + artifacts_json='[]' + while IFS= read -r artifact_name; do + response=$(gh api --method GET \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA/cuda-python/actions/runs/$PUBLIC_RUN_ID/artifacts" \ + -f name="$artifact_name" \ + -F per_page=100) + artifact=$(jq -c -e \ + --arg name "$artifact_name" ' + if .total_count == 1 and + (.artifacts | length) == 1 and + .artifacts[0].name == $name and + .artifacts[0].expired == false + then .artifacts[0] | {id, name, digest} + else error("expected exactly one unexpired artifact named " + $name) + end + ' <<< "$response") + artifacts_json=$(jq -c \ + --argjson artifact "$artifact" \ + '. + [$artifact] | sort_by(.name)' <<< "$artifacts_json") + done < <(jq -r '.[]' <<< "$expected_names") jq -e ' length == 3 and all(.[ ]; @@ -96,10 +112,11 @@ jobs: correlation_id="v1:381173759:$PUBLIC_RUN_ID:$public_run_attempt:$public_sha" echo "artifacts_json=$artifacts_json" >> "$GITHUB_OUTPUT" echo "correlation_id=$correlation_id" >> "$GITHUB_OUTPUT" + echo "public_producer_job_id=$public_producer_job_id" >> "$GITHUB_OUTPUT" echo "public_run_attempt=$public_run_attempt" >> "$GITHUB_OUTPUT" echo "public_sha=$public_sha" >> "$GITHUB_OUTPUT" { - echo "Resolved public run \`$PUBLIC_RUN_ID\` attempt \`$public_run_attempt\`." + echo "Resolved public run \`$PUBLIC_RUN_ID\` attempt \`$public_run_attempt\` and producer job \`$public_producer_job_id\`." echo "" echo "Exact Windows Arm64 artifacts:" jq -r '.[] | "- `\(.name)` (`\(.digest)`)"' <<< "$artifacts_json" @@ -128,12 +145,14 @@ jobs: GH_TOKEN: ${{ steps.private-app-token.outputs.token }} PUBLIC_RUN_ATTEMPT: ${{ needs.resolve-public-build.outputs.public-run-attempt }} PUBLIC_RUN_ID: ${{ inputs.public_run_id }} + PUBLIC_PRODUCER_JOB_ID: ${{ needs.resolve-public-build.outputs.public-producer-job-id }} PUBLIC_SHA: ${{ needs.resolve-public-build.outputs.public-sha }} run: | set -euo pipefail payload=$(jq -n \ --arg artifacts_json "$ARTIFACTS_JSON" \ --arg correlation_id "$CORRELATION_ID" \ + --arg producer_job_id "$PUBLIC_PRODUCER_JOB_ID" \ --arg run_attempt "$PUBLIC_RUN_ATTEMPT" \ --arg run_id "$PUBLIC_RUN_ID" \ --arg sha "$PUBLIC_SHA" \ @@ -145,6 +164,7 @@ jobs: public_repository: "NVIDIA/cuda-python", public_repository_id: "381173759", public_workflow_id: "155304118", + public_producer_job_id: $producer_job_id, public_run_id: $run_id, public_run_attempt: $run_attempt, public_sha: $sha, From 9cd019a72c4f18230f28a46680296d9927ee5ae7 Mon Sep 17 00:00:00 2001 From: isVoid <13521008+isVoid@users.noreply.github.com> Date: Tue, 6 Oct 2026 06:27:47 +0000 Subject: [PATCH 05/13] ci: dispatch exact WoA producer job --- .github/workflows/woa-main-automation.yml | 5 ++ ci/tools/woa_xrepo_select.py | 56 +++++++++++++---------- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/.github/workflows/woa-main-automation.yml b/.github/workflows/woa-main-automation.yml index c5b1c0287c6..4ce1ecd46cb 100644 --- a/.github/workflows/woa-main-automation.yml +++ b/.github/workflows/woa-main-automation.yml @@ -39,6 +39,7 @@ jobs: commit-count: ${{ steps.select.outputs.commit_count }} correlation-id: ${{ steps.select.outputs.correlation_id }} dispatch: ${{ steps.select.outputs.dispatch }} + public-producer-job-id: ${{ steps.select.outputs.public_producer_job_id }} public-run-attempt: ${{ steps.select.outputs.public_run_attempt }} public-run-id: ${{ steps.select.outputs.public_run_id }} public-sha: ${{ steps.select.outputs.public_sha }} @@ -69,6 +70,7 @@ jobs: echo "baseline_sha=$(jq -r '.baseline_sha // ""' <<< "$selection")" >> "$GITHUB_OUTPUT" echo "commit_count=$(jq -r '.commit_count // 0' <<< "$selection")" >> "$GITHUB_OUTPUT" echo "correlation_id=$(jq -r '.correlation_id // ""' <<< "$selection")" >> "$GITHUB_OUTPUT" + echo "public_producer_job_id=$(jq -r '.producer_job_id // ""' <<< "$selection")" >> "$GITHUB_OUTPUT" echo "public_run_attempt=$(jq -r '.run_attempt // ""' <<< "$selection")" >> "$GITHUB_OUTPUT" echo "public_run_id=$(jq -r '.run_id // ""' <<< "$selection")" >> "$GITHUB_OUTPUT" echo "public_sha=$(jq -r '.sha // ""' <<< "$selection")" >> "$GITHUB_OUTPUT" @@ -98,6 +100,7 @@ jobs: COMMIT_COUNT: ${{ needs.select.outputs.commit-count }} CORRELATION_ID: ${{ needs.select.outputs.correlation-id }} GH_TOKEN: ${{ steps.private-app-token.outputs.token }} + PUBLIC_PRODUCER_JOB_ID: ${{ needs.select.outputs.public-producer-job-id }} PUBLIC_RUN_ATTEMPT: ${{ needs.select.outputs.public-run-attempt }} PUBLIC_RUN_ID: ${{ needs.select.outputs.public-run-id }} PUBLIC_SHA: ${{ needs.select.outputs.public-sha }} @@ -108,6 +111,7 @@ jobs: --arg baseline_sha "$BASELINE_SHA" \ --arg commit_count "$COMMIT_COUNT" \ --arg correlation_id "$CORRELATION_ID" \ + --arg producer_job_id "$PUBLIC_PRODUCER_JOB_ID" \ --arg run_attempt "$PUBLIC_RUN_ATTEMPT" \ --arg run_id "$PUBLIC_RUN_ID" \ --arg sha "$PUBLIC_SHA" \ @@ -119,6 +123,7 @@ jobs: public_repository: "NVIDIA/cuda-python", public_repository_id: "381173759", public_workflow_id: "155304118", + public_producer_job_id: $producer_job_id, public_run_id: $run_id, public_run_attempt: $run_attempt, public_sha: $sha, diff --git a/ci/tools/woa_xrepo_select.py b/ci/tools/woa_xrepo_select.py index 4fd9740cf9b..7263d852584 100644 --- a/ci/tools/woa_xrepo_select.py +++ b/ci/tools/woa_xrepo_select.py @@ -18,6 +18,7 @@ PUBLIC_WORKFLOW_PATH = ".github/workflows/ci.yml" REPORTING_APP_ID = 4954254 CHECK_NAME = "cuda-python WoA integration" +PRODUCER_JOB_NAME = "Build win-arm64, CUDA 13.4.2 / py3.13" MAX_LEDGER_COMMITS = 100 @@ -67,31 +68,34 @@ def parse_time(value): return datetime.datetime.fromisoformat(value.replace("Z", "+00:00")) -def select_artifacts(artifacts, sha): - binding_name = f"cuda-bindings-python313-cuda13.4.2-win-arm64-{sha}" - core_name = f"cuda-core-python313-win-arm64-{sha}" - selected = [ - artifact - for artifact in artifacts - if not artifact["expired"] - and ( - artifact["name"] == "cuda-pathfinder-wheel" - or artifact["name"] == binding_name - or artifact["name"] == core_name - ) +def select_artifacts(api, run_id, sha): + names = [ + "cuda-pathfinder-wheel", + f"cuda-bindings-python313-cuda13.4.2-win-arm64-{sha}", + f"cuda-core-python313-win-arm64-{sha}", ] - if len(selected) != 3: - return None - names = {artifact["name"] for artifact in selected} - if len(names) != 3: - return None result = [] - for artifact in sorted(selected, key=lambda item: item["name"]): + for name in names: + response = api.get( + f"repos/{PUBLIC_REPOSITORY}/actions/runs/{run_id}/artifacts", + {"name": name, "per_page": 100}, + ) + artifacts = response["artifacts"] + if response["total_count"] != 1 or len(artifacts) != 1: + return None + artifact = artifacts[0] digest = artifact.get("digest") - if not isinstance(digest, str) or not re.fullmatch(r"sha256:[0-9a-f]{64}", digest): + if not ( + artifact["name"] == name + and artifact["expired"] is False + and isinstance(artifact["id"], int) + and artifact["id"] > 0 + and isinstance(digest, str) + and re.fullmatch(r"sha256:[0-9a-f]{64}", digest) + ): return None result.append({"id": artifact["id"], "name": artifact["name"], "digest": digest}) - return result + return sorted(result, key=lambda item: item["name"]) def validate_run(api, run): @@ -113,12 +117,15 @@ def validate_run(api, run): f"repos/{PUBLIC_REPOSITORY}/actions/runs/{run_id}/attempts/{attempt}/jobs", "jobs", ) - woa_jobs = [job for job in jobs if job["name"].startswith("Build win-arm64, CUDA ")] - if not woa_jobs or any(job["status"] != "completed" or job["conclusion"] != "success" for job in woa_jobs): + producer_jobs = [job for job in jobs if job["name"] == PRODUCER_JOB_NAME] + if not ( + len(producer_jobs) == 1 + and producer_jobs[0]["status"] == "completed" + and producer_jobs[0]["conclusion"] == "success" + ): return None - artifacts = api.paginate(f"repos/{PUBLIC_REPOSITORY}/actions/runs/{run_id}/artifacts", "artifacts") - selected_artifacts = select_artifacts(artifacts, run["head_sha"]) + selected_artifacts = select_artifacts(api, run_id, run["head_sha"]) if selected_artifacts is None: return None @@ -129,6 +136,7 @@ def validate_run(api, run): return { "run_id": str(run_id), "run_attempt": str(attempt), + "producer_job_id": str(producer_jobs[0]["id"]), "sha": run["head_sha"], "artifacts": selected_artifacts, } From 94be0cfc54897181a33f40fae653bb172af71a5f Mon Sep 17 00:00:00 2001 From: isVoid <13521008+isVoid@users.noreply.github.com> Date: Fri, 9 Oct 2026 21:16:07 +0000 Subject: [PATCH 06/13] ci: start WoA validation after artifact builds --- .github/workflows/ci.yml | 19 ++ .github/workflows/woa-crossrepo-smoke.yml | 106 --------- .github/workflows/woa-main-automation.yml | 132 ++++-------- .../woa-private-dispatch-reusable.yml | 149 +++++++++++++ .github/workflows/woa-private-dispatch.yml | 179 +--------------- ci/tools/woa_xrepo_resolve.py | 202 ++++++++++++++++++ ci/tools/woa_xrepo_select.py | 162 +------------- 7 files changed, 424 insertions(+), 525 deletions(-) delete mode 100644 .github/workflows/woa-crossrepo-smoke.yml create mode 100644 .github/workflows/woa-private-dispatch-reusable.yml create mode 100644 ci/tools/woa_xrepo_resolve.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 313be5ff77e..18635230ffb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -466,6 +466,25 @@ jobs: workplan: ${{ needs.detect-changes.outputs.workplan }} single-cuda-major: ${{ needs.ci-vars.outputs.WINDOWS_ARM64_SINGLE_CUDA_MAJOR == 'true' }} + woa-main-dispatch: + name: Dispatch WoA integration + needs: + - build-linux-64 + - build-windows-arm64 + if: ${{ github.repository_owner == 'nvidia' && + github.event_name == 'push' && + github.ref == 'refs/heads/main' && + needs.build-linux-64.result == 'success' && + needs.build-windows-arm64.result == 'success' }} + permissions: + actions: read + checks: read + contents: read + uses: ./.github/workflows/woa-main-automation.yml + with: + public_run_id: ${{ github.run_id }} + secrets: inherit + # NOTE: test-sdist jobs are split by platform (mirroring build-* and test-wheel-*) # so platform-specific sources (e.g. cuda_bindings/*_windows.pyx selected by # build_hooks.py) are exercised on their target OS. Keep these job definitions diff --git a/.github/workflows/woa-crossrepo-smoke.yml b/.github/workflows/woa-crossrepo-smoke.yml deleted file mode 100644 index 4f2fd49f913..00000000000 --- a/.github/workflows/woa-crossrepo-smoke.yml +++ /dev/null @@ -1,106 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# -# SPDX-License-Identifier: Apache-2.0 - -name: "WoA cross-repository transport smoke" -run-name: "WoA transport smoke for PR #${{ inputs.pr_number }}" - -on: - workflow_dispatch: - inputs: - pr_number: - description: Open same-repository PR to receive the smoke Check Run - required: true - type: number - -permissions: {} - -jobs: - resolve-public-pr: - if: ${{ github.repository == 'NVIDIA/cuda-python' }} - runs-on: ubuntu-latest - timeout-minutes: 5 - permissions: - pull-requests: read - outputs: - correlation-id: ${{ steps.resolve.outputs.correlation_id }} - public-sha: ${{ steps.resolve.outputs.public_sha }} - steps: - - name: Resolve exact same-repository PR head - id: resolve - env: - GH_TOKEN: ${{ github.token }} - PR_NUMBER: ${{ inputs.pr_number }} - run: | - set -euo pipefail - [[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]] - pr=$(gh api \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - "repos/NVIDIA/cuda-python/pulls/$PR_NUMBER") - jq -e ' - .state == "open" and - .base.ref == "main" and - .head.repo.full_name == "NVIDIA/cuda-python" and - (.head.sha | test("^[0-9a-f]{40}$")) - ' <<< "$pr" >/dev/null - public_sha=$(jq -r .head.sha <<< "$pr") - correlation_id="smoke:381173759:$PR_NUMBER:$public_sha:${GITHUB_RUN_ID}:${GITHUB_RUN_ATTEMPT}" - echo "public_sha=$public_sha" >> "$GITHUB_OUTPUT" - echo "correlation_id=$correlation_id" >> "$GITHUB_OUTPUT" - - dispatch-private-smoke: - needs: resolve-public-pr - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: {} - steps: - - name: Create private dispatch token - id: private-app-token - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 - with: - client-id: ${{ vars.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_CLIENT_ID }} - private-key: ${{ secrets.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_PRIVATE_KEY }} - owner: NVIDIA-dev - repositories: cuda-python-private - permission-actions: write - - - name: Dispatch and verify private smoke workflow - env: - CORRELATION_ID: ${{ needs.resolve-public-pr.outputs.correlation-id }} - GH_TOKEN: ${{ steps.private-app-token.outputs.token }} - PR_NUMBER: ${{ inputs.pr_number }} - PUBLIC_SHA: ${{ needs.resolve-public-pr.outputs.public-sha }} - run: | - set -euo pipefail - payload=$(jq -n \ - --arg correlation_id "$CORRELATION_ID" \ - --arg pr_number "$PR_NUMBER" \ - --arg public_sha "$PUBLIC_SHA" \ - '{ - ref: "ctk-next", - return_run_details: true, - inputs: { - schema_version: "1", - public_repository: "NVIDIA/cuda-python", - public_repository_id: "381173759", - public_pr_number: $pr_number, - public_sha: $public_sha, - correlation_id: $correlation_id - } - }') - response=$(gh api \ - --method POST \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - repos/NVIDIA-dev/cuda-python-private/actions/workflows/woa-crossrepo-smoke.yml/dispatches \ - --input - <<< "$payload") - private_run_id=$(jq -er '.workflow_run_id | tostring' <<< "$response") - run=$(gh api \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - "repos/NVIDIA-dev/cuda-python-private/actions/runs/$private_run_id") - jq -e ' - .repository.id == 809898190 and - .path == ".github/workflows/woa-crossrepo-smoke.yml" and - .event == "workflow_dispatch" and - .head_branch == "ctk-next" - ' <<< "$run" >/dev/null - echo "Private transport-smoke workflow accepted the exact dispatch." >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/woa-main-automation.yml b/.github/workflows/woa-main-automation.yml index 4ce1ecd46cb..d1736650b40 100644 --- a/.github/workflows/woa-main-automation.yml +++ b/.github/workflows/woa-main-automation.yml @@ -5,6 +5,12 @@ name: "WoA public-main automation" on: + workflow_call: + inputs: + public_run_id: + description: Exact in-progress public CI run with completed WoA producers + required: true + type: string workflow_run: workflows: - CI @@ -23,7 +29,7 @@ permissions: {} jobs: select: - if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion != 'cancelled' }} + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest timeout-minutes: 15 permissions: @@ -34,15 +40,10 @@ jobs: group: cuda-python-woa-public-selector cancel-in-progress: false outputs: - artifacts-json: ${{ steps.select.outputs.artifacts_json }} baseline-sha: ${{ steps.select.outputs.baseline_sha }} commit-count: ${{ steps.select.outputs.commit_count }} - correlation-id: ${{ steps.select.outputs.correlation_id }} dispatch: ${{ steps.select.outputs.dispatch }} - public-producer-job-id: ${{ steps.select.outputs.public_producer_job_id }} - public-run-attempt: ${{ steps.select.outputs.public_run_attempt }} public-run-id: ${{ steps.select.outputs.public_run_id }} - public-sha: ${{ steps.select.outputs.public_sha }} steps: - name: Checkout trusted selector uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -56,104 +57,36 @@ jobs: env: EVENT_CANDIDATE_RUN_ID: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.id || '' }} GITHUB_TOKEN: ${{ github.token }} - MANUAL_CANDIDATE_RUN_ID: ${{ inputs.public_run_id }} + INPUT_CANDIDATE_RUN_ID: ${{ inputs.public_run_id }} run: | set -euo pipefail candidate_run_id="$EVENT_CANDIDATE_RUN_ID" - if [[ -n "$MANUAL_CANDIDATE_RUN_ID" ]]; then - candidate_run_id="$MANUAL_CANDIDATE_RUN_ID" + if [[ -n "$INPUT_CANDIDATE_RUN_ID" ]]; then + candidate_run_id="$INPUT_CANDIDATE_RUN_ID" fi selection=$(python ci/tools/woa_xrepo_select.py --run-id "$candidate_run_id") jq . <<< "$selection" echo "dispatch=$(jq -r .dispatch <<< "$selection")" >> "$GITHUB_OUTPUT" - echo "artifacts_json=$(jq -c '.artifacts // []' <<< "$selection")" >> "$GITHUB_OUTPUT" echo "baseline_sha=$(jq -r '.baseline_sha // ""' <<< "$selection")" >> "$GITHUB_OUTPUT" echo "commit_count=$(jq -r '.commit_count // 0' <<< "$selection")" >> "$GITHUB_OUTPUT" - echo "correlation_id=$(jq -r '.correlation_id // ""' <<< "$selection")" >> "$GITHUB_OUTPUT" - echo "public_producer_job_id=$(jq -r '.producer_job_id // ""' <<< "$selection")" >> "$GITHUB_OUTPUT" - echo "public_run_attempt=$(jq -r '.run_attempt // ""' <<< "$selection")" >> "$GITHUB_OUTPUT" echo "public_run_id=$(jq -r '.run_id // ""' <<< "$selection")" >> "$GITHUB_OUTPUT" - echo "public_sha=$(jq -r '.sha // ""' <<< "$selection")" >> "$GITHUB_OUTPUT" echo "$(jq -r .reason <<< "$selection")" >> "$GITHUB_STEP_SUMMARY" dispatch: needs: select if: ${{ needs.select.outputs.dispatch == 'true' }} - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: {} - steps: - - name: Create private dispatch token - id: private-app-token - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 - with: - client-id: ${{ vars.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_CLIENT_ID }} - private-key: ${{ secrets.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_PRIVATE_KEY }} - owner: NVIDIA-dev - repositories: cuda-python-private - permission-actions: write - - - name: Dispatch exact private validation - env: - ARTIFACTS_JSON: ${{ needs.select.outputs.artifacts-json }} - BASELINE_SHA: ${{ needs.select.outputs.baseline-sha }} - COMMIT_COUNT: ${{ needs.select.outputs.commit-count }} - CORRELATION_ID: ${{ needs.select.outputs.correlation-id }} - GH_TOKEN: ${{ steps.private-app-token.outputs.token }} - PUBLIC_PRODUCER_JOB_ID: ${{ needs.select.outputs.public-producer-job-id }} - PUBLIC_RUN_ATTEMPT: ${{ needs.select.outputs.public-run-attempt }} - PUBLIC_RUN_ID: ${{ needs.select.outputs.public-run-id }} - PUBLIC_SHA: ${{ needs.select.outputs.public-sha }} - run: | - set -euo pipefail - payload=$(jq -n \ - --arg artifacts_json "$ARTIFACTS_JSON" \ - --arg baseline_sha "$BASELINE_SHA" \ - --arg commit_count "$COMMIT_COUNT" \ - --arg correlation_id "$CORRELATION_ID" \ - --arg producer_job_id "$PUBLIC_PRODUCER_JOB_ID" \ - --arg run_attempt "$PUBLIC_RUN_ATTEMPT" \ - --arg run_id "$PUBLIC_RUN_ID" \ - --arg sha "$PUBLIC_SHA" \ - '{ - ref: "ctk-next", - return_run_details: true, - inputs: { - schema_version: "1", - public_repository: "NVIDIA/cuda-python", - public_repository_id: "381173759", - public_workflow_id: "155304118", - public_producer_job_id: $producer_job_id, - public_run_id: $run_id, - public_run_attempt: $run_attempt, - public_sha: $sha, - artifacts_json: $artifacts_json, - baseline_sha: $baseline_sha, - commit_count: $commit_count, - correlation_id: $correlation_id - } - }') - response=$(gh api \ - --method POST \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - repos/NVIDIA-dev/cuda-python-private/actions/workflows/woa-main-validation.yml/dispatches \ - --input - <<< "$payload") - private_run_id=$(jq -er '.workflow_run_id | tostring' <<< "$response") - private_run=$(gh api \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - "repos/NVIDIA-dev/cuda-python-private/actions/runs/$private_run_id") - jq -e \ - --arg title "WoA validation $CORRELATION_ID" ' - .repository.id == 809898190 and - .path == ".github/workflows/woa-main-validation.yml" and - .event == "workflow_dispatch" and - .head_branch == "ctk-next" and - .display_title == $title - ' <<< "$private_run" >/dev/null - echo "Exact private validation accepted the selected public batch." >> "$GITHUB_STEP_SUMMARY" + permissions: + actions: read + contents: read + uses: ./.github/workflows/woa-private-dispatch-reusable.yml + with: + public_run_id: ${{ needs.select.outputs.public-run-id }} + baseline_sha: ${{ needs.select.outputs.baseline-sha }} + commit_count: ${{ needs.select.outputs.commit-count }} + secrets: inherit - validate-cancellation: - if: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'cancelled' }} + validate-terminal-failure: + if: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.conclusion != 'success' }} runs-on: ubuntu-latest timeout-minutes: 5 permissions: @@ -164,7 +97,7 @@ jobs: public-run-id: ${{ steps.validate.outputs.public_run_id }} public-sha: ${{ steps.validate.outputs.public_sha }} steps: - - name: Validate exact cancelled public run + - name: Validate exact unsuccessful public run id: validate env: GH_TOKEN: ${{ github.token }} @@ -181,7 +114,14 @@ jobs: .event == "push" and .head_branch == "main" and .status == "completed" and - .conclusion == "cancelled" + (.conclusion | IN( + "action_required", + "cancelled", + "failure", + "stale", + "startup_failure", + "timed_out" + )) ' <<< "$run" >/dev/null public_sha=$(jq -r .head_sha <<< "$run") public_run_attempt=$(jq -r '.run_attempt | tostring' <<< "$run") @@ -192,7 +132,7 @@ jobs: echo "correlation_id=$correlation_id" >> "$GITHUB_OUTPUT" dispatch-cancellation: - needs: validate-cancellation + needs: validate-terminal-failure runs-on: ubuntu-latest timeout-minutes: 10 permissions: {} @@ -209,11 +149,11 @@ jobs: - name: Locate exact private validation and dispatch cancellation env: - CORRELATION_ID: ${{ needs.validate-cancellation.outputs.correlation-id }} + CORRELATION_ID: ${{ needs.validate-terminal-failure.outputs.correlation-id }} GH_TOKEN: ${{ steps.private-app-token.outputs.token }} - PUBLIC_RUN_ATTEMPT: ${{ needs.validate-cancellation.outputs.public-run-attempt }} - PUBLIC_RUN_ID: ${{ needs.validate-cancellation.outputs.public-run-id }} - PUBLIC_SHA: ${{ needs.validate-cancellation.outputs.public-sha }} + PUBLIC_RUN_ATTEMPT: ${{ needs.validate-terminal-failure.outputs.public-run-attempt }} + PUBLIC_RUN_ID: ${{ needs.validate-terminal-failure.outputs.public-run-id }} + PUBLIC_SHA: ${{ needs.validate-terminal-failure.outputs.public-sha }} run: | set -euo pipefail runs=$(gh api \ @@ -225,7 +165,7 @@ jobs: ' <<< "$runs") match_count=$(jq length <<< "$matches") if (( match_count == 0 )); then - echo "No correlated private validation exists; cancellation is a no-op." >> "$GITHUB_STEP_SUMMARY" + echo "No correlated private validation exists; terminal reconciliation is a no-op." >> "$GITHUB_STEP_SUMMARY" exit 0 fi if (( match_count != 1 )); then diff --git a/.github/workflows/woa-private-dispatch-reusable.yml b/.github/workflows/woa-private-dispatch-reusable.yml new file mode 100644 index 00000000000..9b54b3f9511 --- /dev/null +++ b/.github/workflows/woa-private-dispatch-reusable.yml @@ -0,0 +1,149 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +name: "WoA reusable private dispatch" +run-name: "WoA broker for public run ${{ inputs.public_run_id }}" + +on: + workflow_call: + inputs: + public_run_id: + description: Exact eligible public CI run ID + required: true + type: string + baseline_sha: + description: Last successfully validated public main SHA, if any + required: false + default: "" + type: string + commit_count: + description: Number of public main commits represented by this validation + required: false + default: "1" + type: string + secrets: + CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_PRIVATE_KEY: + required: true + +permissions: {} + +jobs: + resolve-public-build: + if: ${{ github.repository == 'NVIDIA/cuda-python' }} + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + actions: read + contents: read + outputs: + artifacts-json: ${{ steps.resolve.outputs.artifacts_json }} + correlation-id: ${{ steps.resolve.outputs.correlation_id }} + public-producer-jobs-json: ${{ steps.resolve.outputs.public_producer_jobs_json }} + public-run-attempt: ${{ steps.resolve.outputs.public_run_attempt }} + public-sha: ${{ steps.resolve.outputs.public_sha }} + steps: + - name: Checkout trusted resolver + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 1 + persist-credentials: false + + - name: Resolve exact successful public producers + id: resolve + env: + GITHUB_TOKEN: ${{ github.token }} + PUBLIC_RUN_ID: ${{ inputs.public_run_id }} + run: | + set -euo pipefail + resolution=$(python ci/tools/woa_xrepo_resolve.py --run-id "$PUBLIC_RUN_ID") + jq . <<< "$resolution" + echo "artifacts_json=$(jq -c .artifacts <<< "$resolution")" >> "$GITHUB_OUTPUT" + echo "correlation_id=$(jq -r .correlation_id <<< "$resolution")" >> "$GITHUB_OUTPUT" + echo "public_producer_jobs_json=$(jq -c .producer_jobs <<< "$resolution")" >> "$GITHUB_OUTPUT" + echo "public_run_attempt=$(jq -r .run_attempt <<< "$resolution")" >> "$GITHUB_OUTPUT" + echo "public_sha=$(jq -r .sha <<< "$resolution")" >> "$GITHUB_OUTPUT" + { + echo "Resolved public run \`$PUBLIC_RUN_ID\` attempt \`$(jq -r .run_attempt <<< "$resolution")\`." + echo + echo "Verified producers:" + jq -r '.producer_jobs[] | "- `\(.name)` (job `\(.id)`)"' <<< "$resolution" + echo + echo "Exact Windows Arm64 artifacts:" + jq -r '.artifacts[] | "- `\(.name)` (`\(.digest)`)"' <<< "$resolution" + } >> "$GITHUB_STEP_SUMMARY" + + dispatch-private-validation: + needs: resolve-public-build + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: {} + steps: + - name: Create private dispatch token + id: private-app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 + with: + client-id: ${{ vars.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_CLIENT_ID }} + private-key: ${{ secrets.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_PRIVATE_KEY }} + owner: NVIDIA-dev + repositories: cuda-python-private + permission-actions: write + + - name: Dispatch and verify exact private validation + env: + ARTIFACTS_JSON: ${{ needs.resolve-public-build.outputs.artifacts-json }} + BASELINE_SHA: ${{ inputs.baseline_sha }} + COMMIT_COUNT: ${{ inputs.commit_count }} + CORRELATION_ID: ${{ needs.resolve-public-build.outputs.correlation-id }} + GH_TOKEN: ${{ steps.private-app-token.outputs.token }} + PUBLIC_PRODUCER_JOBS_JSON: ${{ needs.resolve-public-build.outputs.public-producer-jobs-json }} + PUBLIC_RUN_ATTEMPT: ${{ needs.resolve-public-build.outputs.public-run-attempt }} + PUBLIC_RUN_ID: ${{ inputs.public_run_id }} + PUBLIC_SHA: ${{ needs.resolve-public-build.outputs.public-sha }} + run: | + set -euo pipefail + payload=$(jq -n \ + --arg artifacts_json "$ARTIFACTS_JSON" \ + --arg baseline_sha "$BASELINE_SHA" \ + --arg commit_count "$COMMIT_COUNT" \ + --arg correlation_id "$CORRELATION_ID" \ + --arg producer_jobs_json "$PUBLIC_PRODUCER_JOBS_JSON" \ + --arg run_attempt "$PUBLIC_RUN_ATTEMPT" \ + --arg run_id "$PUBLIC_RUN_ID" \ + --arg sha "$PUBLIC_SHA" ' + { + ref: "ctk-next", + return_run_details: true, + inputs: { + schema_version: "2", + public_repository: "NVIDIA/cuda-python", + public_repository_id: "381173759", + public_workflow_id: "155304118", + public_producer_jobs_json: $producer_jobs_json, + public_run_id: $run_id, + public_run_attempt: $run_attempt, + public_sha: $sha, + artifacts_json: $artifacts_json, + baseline_sha: $baseline_sha, + commit_count: $commit_count, + correlation_id: $correlation_id + } + }') + response=$(gh api \ + --method POST \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + repos/NVIDIA-dev/cuda-python-private/actions/workflows/woa-main-validation.yml/dispatches \ + --input - <<< "$payload") + private_run_id=$(jq -er '.workflow_run_id | tostring' <<< "$response") + private_run=$(gh api \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA-dev/cuda-python-private/actions/runs/$private_run_id") + jq -e \ + --arg title "WoA validation $CORRELATION_ID" ' + .repository.id == 809898190 and + .path == ".github/workflows/woa-main-validation.yml" and + .event == "workflow_dispatch" and + .head_branch == "ctk-next" and + .display_title == $title + ' <<< "$private_run" >/dev/null + echo "Exact private validation accepted the selected public batch." >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/woa-private-dispatch.yml b/.github/workflows/woa-private-dispatch.yml index e01fd107243..b4820f79f54 100644 --- a/.github/workflows/woa-private-dispatch.yml +++ b/.github/workflows/woa-private-dispatch.yml @@ -9,184 +9,21 @@ on: workflow_dispatch: inputs: public_run_id: - description: Successful public main CI run containing the exact WoA artifacts + description: Eligible public main CI run containing the exact WoA artifacts required: true type: string permissions: {} jobs: - resolve-public-build: + dispatch: if: ${{ github.repository == 'NVIDIA/cuda-python' }} - runs-on: ubuntu-latest - timeout-minutes: 10 permissions: actions: read contents: read - checks: read - outputs: - artifacts-json: ${{ steps.resolve.outputs.artifacts_json }} - correlation-id: ${{ steps.resolve.outputs.correlation_id }} - public-producer-job-id: ${{ steps.resolve.outputs.public_producer_job_id }} - public-run-attempt: ${{ steps.resolve.outputs.public_run_attempt }} - public-sha: ${{ steps.resolve.outputs.public_sha }} - steps: - - name: Resolve exact successful public build - id: resolve - env: - GH_TOKEN: ${{ github.token }} - PUBLIC_RUN_ID: ${{ inputs.public_run_id }} - run: | - set -euo pipefail - [[ "$PUBLIC_RUN_ID" =~ ^[1-9][0-9]*$ ]] - run=$(gh api \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - "repos/NVIDIA/cuda-python/actions/runs/$PUBLIC_RUN_ID") - jq -e ' - .repository.id == 381173759 and - .workflow_id == 155304118 and - .path == ".github/workflows/ci.yml" and - .event == "push" and - .head_branch == "main" and - .status == "completed" and - .conclusion == "success" and - (.head_sha | test("^[0-9a-f]{40}$")) - ' <<< "$run" >/dev/null - public_sha=$(jq -r .head_sha <<< "$run") - public_run_attempt=$(jq -r '.run_attempt | tostring' <<< "$run") - - comparison=$(gh api \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - "repos/NVIDIA/cuda-python/compare/$public_sha...main") - jq -e '.status == "ahead" or .status == "identical"' <<< "$comparison" >/dev/null - - jobs=$(gh api --paginate \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - "repos/NVIDIA/cuda-python/actions/runs/$PUBLIC_RUN_ID/attempts/$public_run_attempt/jobs?per_page=100" \ - --jq '.jobs') - producer_jobs=$(jq -s -c ' - add - | map(select(.name == "Build win-arm64, CUDA 13.4.2 / py3.13")) - ' <<< "$jobs") - jq -e ' - length == 1 and - .[0].status == "completed" and - .[0].conclusion == "success" - ' <<< "$producer_jobs" >/dev/null - public_producer_job_id=$(jq -r '.[0].id | tostring' <<< "$producer_jobs") - - expected_names=$(jq -n \ - --arg sha "$public_sha" '[ - "cuda-pathfinder-wheel", - ("cuda-bindings-python313-cuda13.4.2-win-arm64-" + $sha), - ("cuda-core-python313-win-arm64-" + $sha) - ]') - artifacts_json='[]' - while IFS= read -r artifact_name; do - response=$(gh api --method GET \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - "repos/NVIDIA/cuda-python/actions/runs/$PUBLIC_RUN_ID/artifacts" \ - -f name="$artifact_name" \ - -F per_page=100) - artifact=$(jq -c -e \ - --arg name "$artifact_name" ' - if .total_count == 1 and - (.artifacts | length) == 1 and - .artifacts[0].name == $name and - .artifacts[0].expired == false - then .artifacts[0] | {id, name, digest} - else error("expected exactly one unexpired artifact named " + $name) - end - ' <<< "$response") - artifacts_json=$(jq -c \ - --argjson artifact "$artifact" \ - '. + [$artifact] | sort_by(.name)' <<< "$artifacts_json") - done < <(jq -r '.[]' <<< "$expected_names") - jq -e ' - length == 3 and - all(.[ ]; - (.id | type == "number") and - (.digest | test("^sha256:[0-9a-f]{64}$"))) - ' <<< "$artifacts_json" >/dev/null - - correlation_id="v1:381173759:$PUBLIC_RUN_ID:$public_run_attempt:$public_sha" - echo "artifacts_json=$artifacts_json" >> "$GITHUB_OUTPUT" - echo "correlation_id=$correlation_id" >> "$GITHUB_OUTPUT" - echo "public_producer_job_id=$public_producer_job_id" >> "$GITHUB_OUTPUT" - echo "public_run_attempt=$public_run_attempt" >> "$GITHUB_OUTPUT" - echo "public_sha=$public_sha" >> "$GITHUB_OUTPUT" - { - echo "Resolved public run \`$PUBLIC_RUN_ID\` attempt \`$public_run_attempt\` and producer job \`$public_producer_job_id\`." - echo "" - echo "Exact Windows Arm64 artifacts:" - jq -r '.[] | "- `\(.name)` (`\(.digest)`)"' <<< "$artifacts_json" - } >> "$GITHUB_STEP_SUMMARY" - - dispatch-private-validation: - needs: resolve-public-build - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: {} - steps: - - name: Create private dispatch token - id: private-app-token - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 - with: - client-id: ${{ vars.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_CLIENT_ID }} - private-key: ${{ secrets.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_PRIVATE_KEY }} - owner: NVIDIA-dev - repositories: cuda-python-private - permission-actions: write - - - name: Dispatch and verify exact private validation - env: - ARTIFACTS_JSON: ${{ needs.resolve-public-build.outputs.artifacts-json }} - CORRELATION_ID: ${{ needs.resolve-public-build.outputs.correlation-id }} - GH_TOKEN: ${{ steps.private-app-token.outputs.token }} - PUBLIC_RUN_ATTEMPT: ${{ needs.resolve-public-build.outputs.public-run-attempt }} - PUBLIC_RUN_ID: ${{ inputs.public_run_id }} - PUBLIC_PRODUCER_JOB_ID: ${{ needs.resolve-public-build.outputs.public-producer-job-id }} - PUBLIC_SHA: ${{ needs.resolve-public-build.outputs.public-sha }} - run: | - set -euo pipefail - payload=$(jq -n \ - --arg artifacts_json "$ARTIFACTS_JSON" \ - --arg correlation_id "$CORRELATION_ID" \ - --arg producer_job_id "$PUBLIC_PRODUCER_JOB_ID" \ - --arg run_attempt "$PUBLIC_RUN_ATTEMPT" \ - --arg run_id "$PUBLIC_RUN_ID" \ - --arg sha "$PUBLIC_SHA" \ - '{ - ref: "ctk-next", - return_run_details: true, - inputs: { - schema_version: "1", - public_repository: "NVIDIA/cuda-python", - public_repository_id: "381173759", - public_workflow_id: "155304118", - public_producer_job_id: $producer_job_id, - public_run_id: $run_id, - public_run_attempt: $run_attempt, - public_sha: $sha, - artifacts_json: $artifacts_json, - baseline_sha: "", - commit_count: "1", - correlation_id: $correlation_id - } - }') - response=$(gh api \ - --method POST \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - repos/NVIDIA-dev/cuda-python-private/actions/workflows/woa-main-validation.yml/dispatches \ - --input - <<< "$payload") - private_run_id=$(jq -er '.workflow_run_id | tostring' <<< "$response") - run=$(gh api \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - "repos/NVIDIA-dev/cuda-python-private/actions/runs/$private_run_id") - jq -e ' - .repository.id == 809898190 and - .path == ".github/workflows/woa-main-validation.yml" and - .event == "workflow_dispatch" and - .head_branch == "ctk-next" - ' <<< "$run" >/dev/null - echo "Private validation workflow accepted the exact dispatch." >> "$GITHUB_STEP_SUMMARY" + uses: ./.github/workflows/woa-private-dispatch-reusable.yml + with: + public_run_id: ${{ inputs.public_run_id }} + baseline_sha: "" + commit_count: "1" + secrets: inherit diff --git a/ci/tools/woa_xrepo_resolve.py b/ci/tools/woa_xrepo_resolve.py new file mode 100644 index 00000000000..cb299af13dd --- /dev/null +++ b/ci/tools/woa_xrepo_resolve.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +import argparse +import http.client +import json +import os +import re +import sys +import urllib.parse + +PUBLIC_REPOSITORY = "NVIDIA/cuda-python" +PUBLIC_REPOSITORY_ID = 381173759 +PUBLIC_WORKFLOW_ID = 155304118 +PUBLIC_WORKFLOW_PATH = ".github/workflows/ci.yml" +REPORTING_APP_ID = 4954254 +CHECK_NAME = "cuda-python WoA integration" +CUDA_BUILD_VERSION = "13.4.2" +PYTHON_VERSIONS = ("3.11", "3.12", "3.13", "3.14", "3.14t", "3.15", "3.15t") + + +class GitHubAPI: + def __init__(self, token): + self._token = token + + def get(self, path, params=None): + request_path = f"/{path.lstrip('/')}" + if params: + request_path = f"{request_path}?{urllib.parse.urlencode(params)}" + connection = http.client.HTTPSConnection("api.github.com") + try: + connection.request( + "GET", + request_path, + headers={ + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {self._token}", + "X-GitHub-Api-Version": "2026-03-10", + "User-Agent": "cuda-python-woa-resolver", + }, + ) + response = connection.getresponse() + body = response.read().decode("utf-8", errors="replace") + if response.status >= 400: + raise RuntimeError(f"GitHub API GET {path} failed: {response.status}: {body}") + return json.loads(body) + finally: + connection.close() + + def paginate(self, path, item_key, params=None, max_pages=10): + items = [] + query = dict(params or {}) + query["per_page"] = 100 + for page in range(1, max_pages + 1): + query["page"] = page + response = self.get(path, query) + page_items = response[item_key] + items.extend(page_items) + if len(page_items) < 100: + break + return items + + +def expected_producer_names(): + names = [f"Build linux-64, CUDA {CUDA_BUILD_VERSION} / py3.10"] + names.extend(f"Build win-arm64, CUDA {CUDA_BUILD_VERSION} / py{version}" for version in PYTHON_VERSIONS) + return names + + +def expected_artifact_names(sha): + names = ["cuda-pathfinder-wheel"] + for version in PYTHON_VERSIONS: + python_tag = version.replace(".", "") + names.extend( + ( + f"cuda-bindings-python{python_tag}-cuda{CUDA_BUILD_VERSION}-win-arm64-{sha}", + f"cuda-core-python{python_tag}-win-arm64-{sha}", + ) + ) + return names + + +def select_artifacts(api, run_id, sha): + result = [] + for name in expected_artifact_names(sha): + response = api.get( + f"repos/{PUBLIC_REPOSITORY}/actions/runs/{run_id}/artifacts", + {"name": name, "per_page": 100}, + ) + artifacts = response["artifacts"] + if response["total_count"] != 1 or len(artifacts) != 1: + return None + artifact = artifacts[0] + digest = artifact.get("digest") + if not ( + artifact["name"] == name + and artifact["expired"] is False + and isinstance(artifact["id"], int) + and artifact["id"] > 0 + and isinstance(digest, str) + and re.fullmatch(r"sha256:[0-9a-f]{64}", digest) + ): + return None + result.append({"id": artifact["id"], "name": artifact["name"], "digest": digest}) + return sorted(result, key=lambda item: item["name"]) + + +def run_is_admissible(run): + state_is_admissible = (run["status"] == "in_progress" and run["conclusion"] is None) or ( + run["status"] == "completed" and run["conclusion"] == "success" + ) + return ( + run["repository"]["id"] == PUBLIC_REPOSITORY_ID + and run["workflow_id"] == PUBLIC_WORKFLOW_ID + and run["path"] == PUBLIC_WORKFLOW_PATH + and run["event"] == "push" + and run["head_branch"] == "main" + and state_is_admissible + and re.fullmatch(r"[0-9a-f]{40}", run["head_sha"]) + ) + + +def validate_run(api, run): + if not run_is_admissible(run): + return None + + run_id = run["id"] + attempt = run["run_attempt"] + jobs = api.paginate( + f"repos/{PUBLIC_REPOSITORY}/actions/runs/{run_id}/attempts/{attempt}/jobs", + "jobs", + ) + producer_jobs = [] + for name in expected_producer_names(): + matches = [job for job in jobs if job["name"] == name] + if not (len(matches) == 1 and matches[0]["status"] == "completed" and matches[0]["conclusion"] == "success"): + return None + producer_jobs.append({"id": matches[0]["id"], "name": name}) + + selected_artifacts = select_artifacts(api, run_id, run["head_sha"]) + if selected_artifacts is None: + return None + + comparison = api.get(f"repos/{PUBLIC_REPOSITORY}/compare/{run['head_sha']}...main") + if comparison["status"] not in {"ahead", "identical"}: + return None + + return { + "run_id": str(run_id), + "run_attempt": str(attempt), + "producer_jobs": sorted(producer_jobs, key=lambda item: item["name"]), + "sha": run["head_sha"], + "artifacts": selected_artifacts, + "correlation_id": f"v1:{PUBLIC_REPOSITORY_ID}:{run_id}:{attempt}:{run['head_sha']}", + } + + +def resolve_candidate(api, run_id): + if run_id: + run = api.get(f"repos/{PUBLIC_REPOSITORY}/actions/runs/{run_id}") + return validate_run(api, run) + + runs = api.get( + f"repos/{PUBLIC_REPOSITORY}/actions/workflows/ci.yml/runs", + { + "branch": "main", + "event": "push", + "status": "completed", + "per_page": 30, + }, + )["workflow_runs"] + for run in runs: + candidate = validate_run(api, run) + if candidate is not None: + return candidate + return None + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--run-id", required=True) + args = parser.parse_args() + if not args.run_id.isdigit(): + parser.error("--run-id must be numeric") + + token = os.environ.get("GITHUB_TOKEN") + if not token: + raise SystemExit("GITHUB_TOKEN is required") + candidate = resolve_candidate(GitHubAPI(token), args.run_id) + if candidate is None: + raise RuntimeError("the requested public run is not ready for WoA validation") + print(json.dumps(candidate, separators=(",", ":"), sort_keys=True)) + + +if __name__ == "__main__": + try: + main() + except Exception as error: + print(f"error: {error}", file=sys.stderr) + raise diff --git a/ci/tools/woa_xrepo_select.py b/ci/tools/woa_xrepo_select.py index 7263d852584..edab836b1a0 100644 --- a/ci/tools/woa_xrepo_select.py +++ b/ci/tools/woa_xrepo_select.py @@ -5,164 +5,25 @@ import argparse import datetime -import http.client import json import os -import re import sys -import urllib.parse -PUBLIC_REPOSITORY = "NVIDIA/cuda-python" -PUBLIC_REPOSITORY_ID = 381173759 -PUBLIC_WORKFLOW_ID = 155304118 -PUBLIC_WORKFLOW_PATH = ".github/workflows/ci.yml" -REPORTING_APP_ID = 4954254 -CHECK_NAME = "cuda-python WoA integration" -PRODUCER_JOB_NAME = "Build win-arm64, CUDA 13.4.2 / py3.13" -MAX_LEDGER_COMMITS = 100 - - -class GitHubAPI: - def __init__(self, token): - self._token = token +from woa_xrepo_resolve import ( + CHECK_NAME, + PUBLIC_REPOSITORY, + REPORTING_APP_ID, + GitHubAPI, + resolve_candidate, +) - def get(self, path, params=None): - request_path = f"/{path.lstrip('/')}" - if params: - request_path = f"{request_path}?{urllib.parse.urlencode(params)}" - connection = http.client.HTTPSConnection("api.github.com") - try: - connection.request( - "GET", - request_path, - headers={ - "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {self._token}", - "X-GitHub-Api-Version": "2026-03-10", - "User-Agent": "cuda-python-woa-selector", - }, - ) - response = connection.getresponse() - body = response.read().decode("utf-8", errors="replace") - if response.status >= 400: - raise RuntimeError(f"GitHub API GET {path} failed: {response.status}: {body}") - return json.loads(body) - finally: - connection.close() - - def paginate(self, path, item_key, params=None, max_pages=10): - items = [] - query = dict(params or {}) - query["per_page"] = 100 - for page in range(1, max_pages + 1): - query["page"] = page - response = self.get(path, query) - page_items = response[item_key] - items.extend(page_items) - if len(page_items) < 100: - break - return items +MAX_LEDGER_COMMITS = 100 def parse_time(value): return datetime.datetime.fromisoformat(value.replace("Z", "+00:00")) -def select_artifacts(api, run_id, sha): - names = [ - "cuda-pathfinder-wheel", - f"cuda-bindings-python313-cuda13.4.2-win-arm64-{sha}", - f"cuda-core-python313-win-arm64-{sha}", - ] - result = [] - for name in names: - response = api.get( - f"repos/{PUBLIC_REPOSITORY}/actions/runs/{run_id}/artifacts", - {"name": name, "per_page": 100}, - ) - artifacts = response["artifacts"] - if response["total_count"] != 1 or len(artifacts) != 1: - return None - artifact = artifacts[0] - digest = artifact.get("digest") - if not ( - artifact["name"] == name - and artifact["expired"] is False - and isinstance(artifact["id"], int) - and artifact["id"] > 0 - and isinstance(digest, str) - and re.fullmatch(r"sha256:[0-9a-f]{64}", digest) - ): - return None - result.append({"id": artifact["id"], "name": artifact["name"], "digest": digest}) - return sorted(result, key=lambda item: item["name"]) - - -def validate_run(api, run): - if not ( - run["repository"]["id"] == PUBLIC_REPOSITORY_ID - and run["workflow_id"] == PUBLIC_WORKFLOW_ID - and run["path"] == PUBLIC_WORKFLOW_PATH - and run["event"] == "push" - and run["head_branch"] == "main" - and run["status"] == "completed" - and run["conclusion"] == "success" - and re.fullmatch(r"[0-9a-f]{40}", run["head_sha"]) - ): - return None - - run_id = run["id"] - attempt = run["run_attempt"] - jobs = api.paginate( - f"repos/{PUBLIC_REPOSITORY}/actions/runs/{run_id}/attempts/{attempt}/jobs", - "jobs", - ) - producer_jobs = [job for job in jobs if job["name"] == PRODUCER_JOB_NAME] - if not ( - len(producer_jobs) == 1 - and producer_jobs[0]["status"] == "completed" - and producer_jobs[0]["conclusion"] == "success" - ): - return None - - selected_artifacts = select_artifacts(api, run_id, run["head_sha"]) - if selected_artifacts is None: - return None - - comparison = api.get(f"repos/{PUBLIC_REPOSITORY}/compare/{run['head_sha']}...main") - if comparison["status"] not in {"ahead", "identical"}: - return None - - return { - "run_id": str(run_id), - "run_attempt": str(attempt), - "producer_job_id": str(producer_jobs[0]["id"]), - "sha": run["head_sha"], - "artifacts": selected_artifacts, - } - - -def resolve_candidate(api, run_id): - if run_id: - run = api.get(f"repos/{PUBLIC_REPOSITORY}/actions/runs/{run_id}") - return validate_run(api, run) - - runs = api.get( - f"repos/{PUBLIC_REPOSITORY}/actions/workflows/ci.yml/runs", - { - "branch": "main", - "event": "push", - "status": "completed", - "per_page": 30, - }, - )["workflow_runs"] - for run in runs: - candidate = validate_run(api, run) - if candidate is not None: - return candidate - return None - - def app_checks(api, sha): response = api.get( f"repos/{PUBLIC_REPOSITORY}/commits/{sha}/check-runs", @@ -184,7 +45,7 @@ def list_commits(api): def select_batch(api, candidate, batch_size, max_wait_seconds, now): candidate_checks = app_checks(api, candidate["sha"]) - external_id = f"woa:v1:{PUBLIC_REPOSITORY_ID}:{candidate['run_id']}:{candidate['run_attempt']}:{candidate['sha']}" + external_id = f"woa:{candidate['correlation_id']}" if any(check.get("external_id") == external_id for check in candidate_checks): return {"dispatch": False, "reason": "candidate already has an App-owned attempt"} @@ -207,7 +68,7 @@ def select_batch(api, candidate, batch_size, max_wait_seconds, now): if baseline_index is None: return { "dispatch": True, - "reason": ("bootstrap: no successful App-owned baseline exists in the bounded ledger"), + "reason": "bootstrap: no successful App-owned baseline exists in the bounded ledger", "baseline_sha": "", "commit_count": 1, } @@ -264,9 +125,6 @@ def main(): datetime.datetime.now(datetime.timezone.utc), ) selection.update(candidate) - selection["correlation_id"] = ( - f"v1:{PUBLIC_REPOSITORY_ID}:{candidate['run_id']}:{candidate['run_attempt']}:{candidate['sha']}" - ) print(json.dumps(selection, separators=(",", ":"), sort_keys=True)) From 7c7674bf58de4bf4bbdb95addd4bef2bc4d77fc9 Mon Sep 17 00:00:00 2001 From: isVoid <13521008+isVoid@users.noreply.github.com> Date: Fri, 9 Oct 2026 21:39:07 +0000 Subject: [PATCH 07/13] ci: reuse the WoA manual broker for automation --- .github/workflows/woa-main-automation.yml | 240 +++++++----------- .../woa-private-dispatch-reusable.yml | 149 ----------- .github/workflows/woa-private-dispatch.yml | 199 ++++++++++++++- ci/tools/woa_xrepo_resolve.py | 202 --------------- ci/tools/woa_xrepo_select.py | 136 ---------- 5 files changed, 286 insertions(+), 640 deletions(-) delete mode 100644 .github/workflows/woa-private-dispatch-reusable.yml delete mode 100644 ci/tools/woa_xrepo_resolve.py delete mode 100644 ci/tools/woa_xrepo_select.py diff --git a/.github/workflows/woa-main-automation.yml b/.github/workflows/woa-main-automation.yml index d1736650b40..8a3b2d203ea 100644 --- a/.github/workflows/woa-main-automation.yml +++ b/.github/workflows/woa-main-automation.yml @@ -8,20 +8,17 @@ on: workflow_call: inputs: public_run_id: - description: Exact in-progress public CI run with completed WoA producers required: true type: string - workflow_run: - workflows: - - CI - types: - - completed + secrets: + CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_PRIVATE_KEY: + required: true schedule: - cron: "17 * * * *" workflow_dispatch: inputs: public_run_id: - description: Optional exact public CI run; empty selects the newest eligible build + description: Optional exact public CI run; empty selects the newest successful build required: false type: string @@ -29,9 +26,9 @@ permissions: {} jobs: select: - if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} + if: ${{ github.repository == 'NVIDIA/cuda-python' }} runs-on: ubuntu-latest - timeout-minutes: 15 + timeout-minutes: 10 permissions: actions: read checks: read @@ -45,158 +42,109 @@ jobs: dispatch: ${{ steps.select.outputs.dispatch }} public-run-id: ${{ steps.select.outputs.public_run_id }} steps: - - name: Checkout trusted selector - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 1 - persist-credentials: false - ref: main - - - name: Select exact public batch + - name: Select public batch id: select - env: - EVENT_CANDIDATE_RUN_ID: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.id || '' }} - GITHUB_TOKEN: ${{ github.token }} - INPUT_CANDIDATE_RUN_ID: ${{ inputs.public_run_id }} - run: | - set -euo pipefail - candidate_run_id="$EVENT_CANDIDATE_RUN_ID" - if [[ -n "$INPUT_CANDIDATE_RUN_ID" ]]; then - candidate_run_id="$INPUT_CANDIDATE_RUN_ID" - fi - selection=$(python ci/tools/woa_xrepo_select.py --run-id "$candidate_run_id") - jq . <<< "$selection" - echo "dispatch=$(jq -r .dispatch <<< "$selection")" >> "$GITHUB_OUTPUT" - echo "baseline_sha=$(jq -r '.baseline_sha // ""' <<< "$selection")" >> "$GITHUB_OUTPUT" - echo "commit_count=$(jq -r '.commit_count // 0' <<< "$selection")" >> "$GITHUB_OUTPUT" - echo "public_run_id=$(jq -r '.run_id // ""' <<< "$selection")" >> "$GITHUB_OUTPUT" - echo "$(jq -r .reason <<< "$selection")" >> "$GITHUB_STEP_SUMMARY" - - dispatch: - needs: select - if: ${{ needs.select.outputs.dispatch == 'true' }} - permissions: - actions: read - contents: read - uses: ./.github/workflows/woa-private-dispatch-reusable.yml - with: - public_run_id: ${{ needs.select.outputs.public-run-id }} - baseline_sha: ${{ needs.select.outputs.baseline-sha }} - commit_count: ${{ needs.select.outputs.commit-count }} - secrets: inherit - - validate-terminal-failure: - if: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.conclusion != 'success' }} - runs-on: ubuntu-latest - timeout-minutes: 5 - permissions: - actions: read - outputs: - correlation-id: ${{ steps.validate.outputs.correlation_id }} - public-run-attempt: ${{ steps.validate.outputs.public_run_attempt }} - public-run-id: ${{ steps.validate.outputs.public_run_id }} - public-sha: ${{ steps.validate.outputs.public_sha }} - steps: - - name: Validate exact unsuccessful public run - id: validate env: GH_TOKEN: ${{ github.token }} - PUBLIC_RUN_ID: ${{ github.event.workflow_run.id }} + INPUT_RUN_ID: ${{ inputs.public_run_id }} run: | set -euo pipefail - run=$(gh api \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - "repos/NVIDIA/cuda-python/actions/runs/$PUBLIC_RUN_ID") + if [[ -n "$INPUT_RUN_ID" ]]; then + run=$(gh api \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA/cuda-python/actions/runs/$INPUT_RUN_ID") + else + run=$(gh api --method GET \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + repos/NVIDIA/cuda-python/actions/workflows/ci.yml/runs \ + -f branch=main -f event=push -f status=success -F per_page=1 \ + --jq '.workflow_runs[0]') + fi jq -e ' .repository.id == 381173759 and .workflow_id == 155304118 and .path == ".github/workflows/ci.yml" and .event == "push" and .head_branch == "main" and - .status == "completed" and - (.conclusion | IN( - "action_required", - "cancelled", - "failure", - "stale", - "startup_failure", - "timed_out" - )) + ((.status == "in_progress" and .conclusion == null) or + (.status == "completed" and .conclusion == "success")) ' <<< "$run" >/dev/null - public_sha=$(jq -r .head_sha <<< "$run") - public_run_attempt=$(jq -r '.run_attempt | tostring' <<< "$run") - correlation_id="v1:381173759:$PUBLIC_RUN_ID:$public_run_attempt:$public_sha" - echo "public_run_id=$PUBLIC_RUN_ID" >> "$GITHUB_OUTPUT" - echo "public_run_attempt=$public_run_attempt" >> "$GITHUB_OUTPUT" - echo "public_sha=$public_sha" >> "$GITHUB_OUTPUT" - echo "correlation_id=$correlation_id" >> "$GITHUB_OUTPUT" - - dispatch-cancellation: - needs: validate-terminal-failure - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: {} - steps: - - name: Create private dispatch token - id: private-app-token - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 - with: - client-id: ${{ vars.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_CLIENT_ID }} - private-key: ${{ secrets.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_PRIVATE_KEY }} - owner: NVIDIA-dev - repositories: cuda-python-private - permission-actions: write + run_id=$(jq -r '.id | tostring' <<< "$run") + attempt=$(jq -r '.run_attempt | tostring' <<< "$run") + sha=$(jq -r .head_sha <<< "$run") + external_id="woa:v1:381173759:$run_id:$attempt:$sha" - - name: Locate exact private validation and dispatch cancellation - env: - CORRELATION_ID: ${{ needs.validate-terminal-failure.outputs.correlation-id }} - GH_TOKEN: ${{ steps.private-app-token.outputs.token }} - PUBLIC_RUN_ATTEMPT: ${{ needs.validate-terminal-failure.outputs.public-run-attempt }} - PUBLIC_RUN_ID: ${{ needs.validate-terminal-failure.outputs.public-run-id }} - PUBLIC_SHA: ${{ needs.validate-terminal-failure.outputs.public-sha }} - run: | - set -euo pipefail - runs=$(gh api \ + checks=$(gh api \ -H 'X-GitHub-Api-Version: 2026-03-10' \ - 'repos/NVIDIA-dev/cuda-python-private/actions/workflows/woa-main-validation.yml/runs?event=workflow_dispatch&per_page=100') - matches=$(jq -c \ - --arg title "WoA validation $CORRELATION_ID" ' - [.workflow_runs[] | select(.display_title == $title)] - ' <<< "$runs") - match_count=$(jq length <<< "$matches") - if (( match_count == 0 )); then - echo "No correlated private validation exists; terminal reconciliation is a no-op." >> "$GITHUB_STEP_SUMMARY" + "repos/NVIDIA/cuda-python/commits/$sha/check-runs?check_name=cuda-python%20WoA%20integration&per_page=100") + if jq -e --arg id "$external_id" ' + any(.check_runs[]; .app.id == 4954254 and .external_id == $id) + ' <<< "$checks" >/dev/null; then + echo "dispatch=false" >> "$GITHUB_OUTPUT" + echo "This exact public run already has an App-owned attempt." >> "$GITHUB_STEP_SUMMARY" exit 0 fi - if (( match_count != 1 )); then - echo "Expected one correlated private validation, found $match_count." >&2 - exit 1 - fi - private_run_id=$(jq -r '.[0].id | tostring' <<< "$matches") - payload=$(jq -n \ - --arg correlation_id "$CORRELATION_ID" \ - --arg private_run_id "$private_run_id" \ - --arg run_attempt "$PUBLIC_RUN_ATTEMPT" \ - --arg run_id "$PUBLIC_RUN_ID" \ - --arg sha "$PUBLIC_SHA" ' - { - ref: "ctk-next", - return_run_details: true, - inputs: { - schema_version: "1", - public_repository_id: "381173759", - public_run_id: $run_id, - public_run_attempt: $run_attempt, - public_sha: $sha, - private_run_id: $private_run_id, - correlation_id: $correlation_id - } - }') - response=$(gh api \ - --method POST \ + commits=$(gh api --method GET \ -H 'X-GitHub-Api-Version: 2026-03-10' \ - repos/NVIDIA-dev/cuda-python-private/actions/workflows/woa-main-cancel.yml/dispatches \ - --input - <<< "$payload") - jq -e '.workflow_run_id | type == "number"' <<< "$response" >/dev/null - echo "Private cancellation controller accepted the exact request." >> "$GITHUB_STEP_SUMMARY" + repos/NVIDIA/cuda-python/commits -f sha=main -F per_page=100) + candidate_index=$(jq -er --arg sha "$sha" ' + [to_entries[] | select(.value.sha == $sha) | .key][0] + ' <<< "$commits") + commit_total=$(jq length <<< "$commits") + baseline_index="" + baseline_sha="" + for ((index = candidate_index + 1; index < commit_total; index++)); do + older_sha=$(jq -r --argjson index "$index" '.[$index].sha' <<< "$commits") + older_checks=$(gh api \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA/cuda-python/commits/$older_sha/check-runs?check_name=cuda-python%20WoA%20integration&per_page=100") + if jq -e ' + any(.check_runs[]; + .app.id == 4954254 and + .status == "completed" and + .conclusion == "success") + ' <<< "$older_checks" >/dev/null; then + baseline_index=$index + baseline_sha=$older_sha + break + fi + done + + dispatch=true + commit_count=1 + reason="Bootstrap: no successful App-owned baseline exists in the latest 100 commits." + if [[ -n "$baseline_index" ]]; then + commit_count=$((baseline_index - candidate_index)) + oldest_pending_index=$((baseline_index - 1)) + oldest_pending_at=$(jq -r \ + --argjson index "$oldest_pending_index" \ + '.[$index].commit.committer.date' <<< "$commits") + age_seconds=$(($(date +%s) - $(date -d "$oldest_pending_at" +%s))) + if (( commit_count >= 3 )); then + reason="Batch threshold reached: $commit_count commits." + elif (( age_seconds >= 7200 )); then + reason="Two-hour fallback reached: oldest pending commit is $age_seconds seconds old." + else + dispatch=false + reason="Deferred: $commit_count/3 commits and oldest pending age $age_seconds/7200 seconds." + fi + fi + echo "baseline_sha=$baseline_sha" >> "$GITHUB_OUTPUT" + echo "commit_count=$commit_count" >> "$GITHUB_OUTPUT" + echo "dispatch=$dispatch" >> "$GITHUB_OUTPUT" + echo "public_run_id=$run_id" >> "$GITHUB_OUTPUT" + echo "$reason" >> "$GITHUB_STEP_SUMMARY" + + dispatch: + needs: select + if: ${{ needs.select.outputs.dispatch == 'true' }} + permissions: + actions: read + contents: read + uses: ./.github/workflows/woa-private-dispatch.yml + with: + public_run_id: ${{ needs.select.outputs.public-run-id }} + baseline_sha: ${{ needs.select.outputs.baseline-sha }} + commit_count: ${{ needs.select.outputs.commit-count }} + secrets: inherit diff --git a/.github/workflows/woa-private-dispatch-reusable.yml b/.github/workflows/woa-private-dispatch-reusable.yml deleted file mode 100644 index 9b54b3f9511..00000000000 --- a/.github/workflows/woa-private-dispatch-reusable.yml +++ /dev/null @@ -1,149 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# -# SPDX-License-Identifier: Apache-2.0 - -name: "WoA reusable private dispatch" -run-name: "WoA broker for public run ${{ inputs.public_run_id }}" - -on: - workflow_call: - inputs: - public_run_id: - description: Exact eligible public CI run ID - required: true - type: string - baseline_sha: - description: Last successfully validated public main SHA, if any - required: false - default: "" - type: string - commit_count: - description: Number of public main commits represented by this validation - required: false - default: "1" - type: string - secrets: - CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_PRIVATE_KEY: - required: true - -permissions: {} - -jobs: - resolve-public-build: - if: ${{ github.repository == 'NVIDIA/cuda-python' }} - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - actions: read - contents: read - outputs: - artifacts-json: ${{ steps.resolve.outputs.artifacts_json }} - correlation-id: ${{ steps.resolve.outputs.correlation_id }} - public-producer-jobs-json: ${{ steps.resolve.outputs.public_producer_jobs_json }} - public-run-attempt: ${{ steps.resolve.outputs.public_run_attempt }} - public-sha: ${{ steps.resolve.outputs.public_sha }} - steps: - - name: Checkout trusted resolver - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 1 - persist-credentials: false - - - name: Resolve exact successful public producers - id: resolve - env: - GITHUB_TOKEN: ${{ github.token }} - PUBLIC_RUN_ID: ${{ inputs.public_run_id }} - run: | - set -euo pipefail - resolution=$(python ci/tools/woa_xrepo_resolve.py --run-id "$PUBLIC_RUN_ID") - jq . <<< "$resolution" - echo "artifacts_json=$(jq -c .artifacts <<< "$resolution")" >> "$GITHUB_OUTPUT" - echo "correlation_id=$(jq -r .correlation_id <<< "$resolution")" >> "$GITHUB_OUTPUT" - echo "public_producer_jobs_json=$(jq -c .producer_jobs <<< "$resolution")" >> "$GITHUB_OUTPUT" - echo "public_run_attempt=$(jq -r .run_attempt <<< "$resolution")" >> "$GITHUB_OUTPUT" - echo "public_sha=$(jq -r .sha <<< "$resolution")" >> "$GITHUB_OUTPUT" - { - echo "Resolved public run \`$PUBLIC_RUN_ID\` attempt \`$(jq -r .run_attempt <<< "$resolution")\`." - echo - echo "Verified producers:" - jq -r '.producer_jobs[] | "- `\(.name)` (job `\(.id)`)"' <<< "$resolution" - echo - echo "Exact Windows Arm64 artifacts:" - jq -r '.artifacts[] | "- `\(.name)` (`\(.digest)`)"' <<< "$resolution" - } >> "$GITHUB_STEP_SUMMARY" - - dispatch-private-validation: - needs: resolve-public-build - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: {} - steps: - - name: Create private dispatch token - id: private-app-token - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 - with: - client-id: ${{ vars.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_CLIENT_ID }} - private-key: ${{ secrets.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_PRIVATE_KEY }} - owner: NVIDIA-dev - repositories: cuda-python-private - permission-actions: write - - - name: Dispatch and verify exact private validation - env: - ARTIFACTS_JSON: ${{ needs.resolve-public-build.outputs.artifacts-json }} - BASELINE_SHA: ${{ inputs.baseline_sha }} - COMMIT_COUNT: ${{ inputs.commit_count }} - CORRELATION_ID: ${{ needs.resolve-public-build.outputs.correlation-id }} - GH_TOKEN: ${{ steps.private-app-token.outputs.token }} - PUBLIC_PRODUCER_JOBS_JSON: ${{ needs.resolve-public-build.outputs.public-producer-jobs-json }} - PUBLIC_RUN_ATTEMPT: ${{ needs.resolve-public-build.outputs.public-run-attempt }} - PUBLIC_RUN_ID: ${{ inputs.public_run_id }} - PUBLIC_SHA: ${{ needs.resolve-public-build.outputs.public-sha }} - run: | - set -euo pipefail - payload=$(jq -n \ - --arg artifacts_json "$ARTIFACTS_JSON" \ - --arg baseline_sha "$BASELINE_SHA" \ - --arg commit_count "$COMMIT_COUNT" \ - --arg correlation_id "$CORRELATION_ID" \ - --arg producer_jobs_json "$PUBLIC_PRODUCER_JOBS_JSON" \ - --arg run_attempt "$PUBLIC_RUN_ATTEMPT" \ - --arg run_id "$PUBLIC_RUN_ID" \ - --arg sha "$PUBLIC_SHA" ' - { - ref: "ctk-next", - return_run_details: true, - inputs: { - schema_version: "2", - public_repository: "NVIDIA/cuda-python", - public_repository_id: "381173759", - public_workflow_id: "155304118", - public_producer_jobs_json: $producer_jobs_json, - public_run_id: $run_id, - public_run_attempt: $run_attempt, - public_sha: $sha, - artifacts_json: $artifacts_json, - baseline_sha: $baseline_sha, - commit_count: $commit_count, - correlation_id: $correlation_id - } - }') - response=$(gh api \ - --method POST \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - repos/NVIDIA-dev/cuda-python-private/actions/workflows/woa-main-validation.yml/dispatches \ - --input - <<< "$payload") - private_run_id=$(jq -er '.workflow_run_id | tostring' <<< "$response") - private_run=$(gh api \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - "repos/NVIDIA-dev/cuda-python-private/actions/runs/$private_run_id") - jq -e \ - --arg title "WoA validation $CORRELATION_ID" ' - .repository.id == 809898190 and - .path == ".github/workflows/woa-main-validation.yml" and - .event == "workflow_dispatch" and - .head_branch == "ctk-next" and - .display_title == $title - ' <<< "$private_run" >/dev/null - echo "Exact private validation accepted the selected public batch." >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/woa-private-dispatch.yml b/.github/workflows/woa-private-dispatch.yml index b4820f79f54..42ec2b728c4 100644 --- a/.github/workflows/woa-private-dispatch.yml +++ b/.github/workflows/woa-private-dispatch.yml @@ -6,6 +6,22 @@ name: "WoA exact public-main dispatch" run-name: "WoA exact dispatch for public run ${{ inputs.public_run_id }}" on: + workflow_call: + inputs: + public_run_id: + required: true + type: string + baseline_sha: + required: false + default: "" + type: string + commit_count: + required: false + default: "1" + type: string + secrets: + CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_PRIVATE_KEY: + required: true workflow_dispatch: inputs: public_run_id: @@ -16,14 +32,183 @@ on: permissions: {} jobs: - dispatch: + resolve-public-build: if: ${{ github.repository == 'NVIDIA/cuda-python' }} + runs-on: ubuntu-latest + timeout-minutes: 10 permissions: actions: read contents: read - uses: ./.github/workflows/woa-private-dispatch-reusable.yml - with: - public_run_id: ${{ inputs.public_run_id }} - baseline_sha: "" - commit_count: "1" - secrets: inherit + outputs: + artifacts-json: ${{ steps.resolve.outputs.artifacts_json }} + correlation-id: ${{ steps.resolve.outputs.correlation_id }} + public-producer-job-id: ${{ steps.resolve.outputs.public_producer_job_id }} + public-run-attempt: ${{ steps.resolve.outputs.public_run_attempt }} + public-sha: ${{ steps.resolve.outputs.public_sha }} + steps: + - name: Resolve exact public build + id: resolve + env: + GH_TOKEN: ${{ github.token }} + PUBLIC_RUN_ID: ${{ inputs.public_run_id }} + run: | + set -euo pipefail + [[ "$PUBLIC_RUN_ID" =~ ^[1-9][0-9]*$ ]] + run=$(gh api \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA/cuda-python/actions/runs/$PUBLIC_RUN_ID") + jq -e ' + .repository.id == 381173759 and + .workflow_id == 155304118 and + .path == ".github/workflows/ci.yml" and + .event == "push" and + .head_branch == "main" and + ((.status == "in_progress" and .conclusion == null) or + (.status == "completed" and .conclusion == "success")) and + (.head_sha | test("^[0-9a-f]{40}$")) + ' <<< "$run" >/dev/null + public_sha=$(jq -r .head_sha <<< "$run") + public_run_attempt=$(jq -r '.run_attempt | tostring' <<< "$run") + + comparison=$(gh api \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA/cuda-python/compare/$public_sha...main") + jq -e '.status == "ahead" or .status == "identical"' <<< "$comparison" >/dev/null + + jobs=$(gh api --paginate \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA/cuda-python/actions/runs/$PUBLIC_RUN_ID/attempts/$public_run_attempt/jobs?per_page=100" \ + --jq '.jobs') + producer_jobs=$(jq -s -c ' + add + | map(select(.name == "Build win-arm64, CUDA 13.4.2 / py3.13")) + ' <<< "$jobs") + jq -e ' + length == 1 and + .[0].status == "completed" and + .[0].conclusion == "success" + ' <<< "$producer_jobs" >/dev/null + public_producer_job_id=$(jq -r '.[0].id | tostring' <<< "$producer_jobs") + + expected_names=$(jq -nc --arg sha "$public_sha" ' + (["cuda-pathfinder-wheel"] + (["311", "312", "313", "314", "314t", "315", "315t"] | + map( + ("cuda-bindings-python" + . + "-cuda13.4.2-win-arm64-" + $sha), + ("cuda-core-python" + . + "-win-arm64-" + $sha) + ))) + ') + artifacts_json='[]' + while IFS= read -r artifact_name; do + response=$(gh api --method GET \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA/cuda-python/actions/runs/$PUBLIC_RUN_ID/artifacts" \ + -f name="$artifact_name" \ + -F per_page=100) + artifact=$(jq -c -e \ + --arg name "$artifact_name" ' + if .total_count == 1 and + (.artifacts | length) == 1 and + .artifacts[0].name == $name and + .artifacts[0].expired == false + then .artifacts[0] | {id, name, digest} + else error("expected exactly one unexpired artifact named " + $name) + end + ' <<< "$response") + artifacts_json=$(jq -c \ + --argjson artifact "$artifact" \ + '. + [$artifact] | sort_by(.name)' <<< "$artifacts_json") + done < <(jq -r '.[]' <<< "$expected_names") + jq -e ' + length == 15 and + all(.[ ]; + (.id | type == "number") and + (.digest | test("^sha256:[0-9a-f]{64}$"))) + ' <<< "$artifacts_json" >/dev/null + + correlation_id="v1:381173759:$PUBLIC_RUN_ID:$public_run_attempt:$public_sha" + echo "artifacts_json=$artifacts_json" >> "$GITHUB_OUTPUT" + echo "correlation_id=$correlation_id" >> "$GITHUB_OUTPUT" + echo "public_producer_job_id=$public_producer_job_id" >> "$GITHUB_OUTPUT" + echo "public_run_attempt=$public_run_attempt" >> "$GITHUB_OUTPUT" + echo "public_sha=$public_sha" >> "$GITHUB_OUTPUT" + { + echo "Resolved public run \`$PUBLIC_RUN_ID\` attempt \`$public_run_attempt\` and producer job \`$public_producer_job_id\`." + echo "" + echo "Exact Windows Arm64 artifacts:" + jq -r '.[] | "- `\(.name)` (`\(.digest)`)"' <<< "$artifacts_json" + } >> "$GITHUB_STEP_SUMMARY" + + dispatch-private-validation: + needs: resolve-public-build + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: {} + steps: + - name: Create private dispatch token + id: private-app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 + with: + client-id: ${{ vars.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_CLIENT_ID }} + private-key: ${{ secrets.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_PRIVATE_KEY }} + owner: NVIDIA-dev + repositories: cuda-python-private + permission-actions: write + + - name: Dispatch and verify exact private validation + env: + ARTIFACTS_JSON: ${{ needs.resolve-public-build.outputs.artifacts-json }} + BASELINE_SHA: ${{ inputs.baseline_sha || '' }} + COMMIT_COUNT: ${{ inputs.commit_count || '1' }} + CORRELATION_ID: ${{ needs.resolve-public-build.outputs.correlation-id }} + GH_TOKEN: ${{ steps.private-app-token.outputs.token }} + PUBLIC_RUN_ATTEMPT: ${{ needs.resolve-public-build.outputs.public-run-attempt }} + PUBLIC_RUN_ID: ${{ inputs.public_run_id }} + PUBLIC_PRODUCER_JOB_ID: ${{ needs.resolve-public-build.outputs.public-producer-job-id }} + PUBLIC_SHA: ${{ needs.resolve-public-build.outputs.public-sha }} + run: | + set -euo pipefail + payload=$(jq -n \ + --arg artifacts_json "$ARTIFACTS_JSON" \ + --arg baseline_sha "$BASELINE_SHA" \ + --arg commit_count "$COMMIT_COUNT" \ + --arg correlation_id "$CORRELATION_ID" \ + --arg producer_job_id "$PUBLIC_PRODUCER_JOB_ID" \ + --arg run_attempt "$PUBLIC_RUN_ATTEMPT" \ + --arg run_id "$PUBLIC_RUN_ID" \ + --arg sha "$PUBLIC_SHA" \ + '{ + ref: "ctk-next", + return_run_details: true, + inputs: { + schema_version: "1", + public_repository: "NVIDIA/cuda-python", + public_repository_id: "381173759", + public_workflow_id: "155304118", + public_producer_job_id: $producer_job_id, + public_run_id: $run_id, + public_run_attempt: $run_attempt, + public_sha: $sha, + artifacts_json: $artifacts_json, + baseline_sha: $baseline_sha, + commit_count: $commit_count, + correlation_id: $correlation_id + } + }') + response=$(gh api \ + --method POST \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + repos/NVIDIA-dev/cuda-python-private/actions/workflows/woa-main-validation.yml/dispatches \ + --input - <<< "$payload") + private_run_id=$(jq -er '.workflow_run_id | tostring' <<< "$response") + run=$(gh api \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA-dev/cuda-python-private/actions/runs/$private_run_id") + jq -e \ + --arg title "WoA validation $CORRELATION_ID" ' + .repository.id == 809898190 and + .path == ".github/workflows/woa-main-validation.yml" and + .event == "workflow_dispatch" and + .head_branch == "ctk-next" and + .display_title == $title + ' <<< "$run" >/dev/null + echo "Private validation workflow accepted the exact dispatch." >> "$GITHUB_STEP_SUMMARY" diff --git a/ci/tools/woa_xrepo_resolve.py b/ci/tools/woa_xrepo_resolve.py deleted file mode 100644 index cb299af13dd..00000000000 --- a/ci/tools/woa_xrepo_resolve.py +++ /dev/null @@ -1,202 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# -# SPDX-License-Identifier: Apache-2.0 - -import argparse -import http.client -import json -import os -import re -import sys -import urllib.parse - -PUBLIC_REPOSITORY = "NVIDIA/cuda-python" -PUBLIC_REPOSITORY_ID = 381173759 -PUBLIC_WORKFLOW_ID = 155304118 -PUBLIC_WORKFLOW_PATH = ".github/workflows/ci.yml" -REPORTING_APP_ID = 4954254 -CHECK_NAME = "cuda-python WoA integration" -CUDA_BUILD_VERSION = "13.4.2" -PYTHON_VERSIONS = ("3.11", "3.12", "3.13", "3.14", "3.14t", "3.15", "3.15t") - - -class GitHubAPI: - def __init__(self, token): - self._token = token - - def get(self, path, params=None): - request_path = f"/{path.lstrip('/')}" - if params: - request_path = f"{request_path}?{urllib.parse.urlencode(params)}" - connection = http.client.HTTPSConnection("api.github.com") - try: - connection.request( - "GET", - request_path, - headers={ - "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {self._token}", - "X-GitHub-Api-Version": "2026-03-10", - "User-Agent": "cuda-python-woa-resolver", - }, - ) - response = connection.getresponse() - body = response.read().decode("utf-8", errors="replace") - if response.status >= 400: - raise RuntimeError(f"GitHub API GET {path} failed: {response.status}: {body}") - return json.loads(body) - finally: - connection.close() - - def paginate(self, path, item_key, params=None, max_pages=10): - items = [] - query = dict(params or {}) - query["per_page"] = 100 - for page in range(1, max_pages + 1): - query["page"] = page - response = self.get(path, query) - page_items = response[item_key] - items.extend(page_items) - if len(page_items) < 100: - break - return items - - -def expected_producer_names(): - names = [f"Build linux-64, CUDA {CUDA_BUILD_VERSION} / py3.10"] - names.extend(f"Build win-arm64, CUDA {CUDA_BUILD_VERSION} / py{version}" for version in PYTHON_VERSIONS) - return names - - -def expected_artifact_names(sha): - names = ["cuda-pathfinder-wheel"] - for version in PYTHON_VERSIONS: - python_tag = version.replace(".", "") - names.extend( - ( - f"cuda-bindings-python{python_tag}-cuda{CUDA_BUILD_VERSION}-win-arm64-{sha}", - f"cuda-core-python{python_tag}-win-arm64-{sha}", - ) - ) - return names - - -def select_artifacts(api, run_id, sha): - result = [] - for name in expected_artifact_names(sha): - response = api.get( - f"repos/{PUBLIC_REPOSITORY}/actions/runs/{run_id}/artifacts", - {"name": name, "per_page": 100}, - ) - artifacts = response["artifacts"] - if response["total_count"] != 1 or len(artifacts) != 1: - return None - artifact = artifacts[0] - digest = artifact.get("digest") - if not ( - artifact["name"] == name - and artifact["expired"] is False - and isinstance(artifact["id"], int) - and artifact["id"] > 0 - and isinstance(digest, str) - and re.fullmatch(r"sha256:[0-9a-f]{64}", digest) - ): - return None - result.append({"id": artifact["id"], "name": artifact["name"], "digest": digest}) - return sorted(result, key=lambda item: item["name"]) - - -def run_is_admissible(run): - state_is_admissible = (run["status"] == "in_progress" and run["conclusion"] is None) or ( - run["status"] == "completed" and run["conclusion"] == "success" - ) - return ( - run["repository"]["id"] == PUBLIC_REPOSITORY_ID - and run["workflow_id"] == PUBLIC_WORKFLOW_ID - and run["path"] == PUBLIC_WORKFLOW_PATH - and run["event"] == "push" - and run["head_branch"] == "main" - and state_is_admissible - and re.fullmatch(r"[0-9a-f]{40}", run["head_sha"]) - ) - - -def validate_run(api, run): - if not run_is_admissible(run): - return None - - run_id = run["id"] - attempt = run["run_attempt"] - jobs = api.paginate( - f"repos/{PUBLIC_REPOSITORY}/actions/runs/{run_id}/attempts/{attempt}/jobs", - "jobs", - ) - producer_jobs = [] - for name in expected_producer_names(): - matches = [job for job in jobs if job["name"] == name] - if not (len(matches) == 1 and matches[0]["status"] == "completed" and matches[0]["conclusion"] == "success"): - return None - producer_jobs.append({"id": matches[0]["id"], "name": name}) - - selected_artifacts = select_artifacts(api, run_id, run["head_sha"]) - if selected_artifacts is None: - return None - - comparison = api.get(f"repos/{PUBLIC_REPOSITORY}/compare/{run['head_sha']}...main") - if comparison["status"] not in {"ahead", "identical"}: - return None - - return { - "run_id": str(run_id), - "run_attempt": str(attempt), - "producer_jobs": sorted(producer_jobs, key=lambda item: item["name"]), - "sha": run["head_sha"], - "artifacts": selected_artifacts, - "correlation_id": f"v1:{PUBLIC_REPOSITORY_ID}:{run_id}:{attempt}:{run['head_sha']}", - } - - -def resolve_candidate(api, run_id): - if run_id: - run = api.get(f"repos/{PUBLIC_REPOSITORY}/actions/runs/{run_id}") - return validate_run(api, run) - - runs = api.get( - f"repos/{PUBLIC_REPOSITORY}/actions/workflows/ci.yml/runs", - { - "branch": "main", - "event": "push", - "status": "completed", - "per_page": 30, - }, - )["workflow_runs"] - for run in runs: - candidate = validate_run(api, run) - if candidate is not None: - return candidate - return None - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("--run-id", required=True) - args = parser.parse_args() - if not args.run_id.isdigit(): - parser.error("--run-id must be numeric") - - token = os.environ.get("GITHUB_TOKEN") - if not token: - raise SystemExit("GITHUB_TOKEN is required") - candidate = resolve_candidate(GitHubAPI(token), args.run_id) - if candidate is None: - raise RuntimeError("the requested public run is not ready for WoA validation") - print(json.dumps(candidate, separators=(",", ":"), sort_keys=True)) - - -if __name__ == "__main__": - try: - main() - except Exception as error: - print(f"error: {error}", file=sys.stderr) - raise diff --git a/ci/tools/woa_xrepo_select.py b/ci/tools/woa_xrepo_select.py deleted file mode 100644 index edab836b1a0..00000000000 --- a/ci/tools/woa_xrepo_select.py +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# -# SPDX-License-Identifier: Apache-2.0 - -import argparse -import datetime -import json -import os -import sys - -from woa_xrepo_resolve import ( - CHECK_NAME, - PUBLIC_REPOSITORY, - REPORTING_APP_ID, - GitHubAPI, - resolve_candidate, -) - -MAX_LEDGER_COMMITS = 100 - - -def parse_time(value): - return datetime.datetime.fromisoformat(value.replace("Z", "+00:00")) - - -def app_checks(api, sha): - response = api.get( - f"repos/{PUBLIC_REPOSITORY}/commits/{sha}/check-runs", - {"check_name": CHECK_NAME, "per_page": 100}, - ) - return [ - check - for check in response["check_runs"] - if check["name"] == CHECK_NAME and check["app"]["id"] == REPORTING_APP_ID - ] - - -def list_commits(api): - return api.get( - f"repos/{PUBLIC_REPOSITORY}/commits", - {"sha": "main", "per_page": MAX_LEDGER_COMMITS}, - ) - - -def select_batch(api, candidate, batch_size, max_wait_seconds, now): - candidate_checks = app_checks(api, candidate["sha"]) - external_id = f"woa:{candidate['correlation_id']}" - if any(check.get("external_id") == external_id for check in candidate_checks): - return {"dispatch": False, "reason": "candidate already has an App-owned attempt"} - - commits = list_commits(api) - try: - candidate_index = next(index for index, commit in enumerate(commits) if commit["sha"] == candidate["sha"]) - except StopIteration as error: - raise RuntimeError(f"candidate SHA was not found on the latest {MAX_LEDGER_COMMITS} main commits") from error - - baseline_index = None - baseline_sha = "" - for index in range(candidate_index + 1, len(commits)): - checks = app_checks(api, commits[index]["sha"]) - successful = [check for check in checks if check["status"] == "completed" and check["conclusion"] == "success"] - if successful: - baseline_index = index - baseline_sha = commits[index]["sha"] - break - - if baseline_index is None: - return { - "dispatch": True, - "reason": "bootstrap: no successful App-owned baseline exists in the bounded ledger", - "baseline_sha": "", - "commit_count": 1, - } - - commit_count = baseline_index - candidate_index - oldest_pending = commits[baseline_index - 1] - oldest_time = parse_time(oldest_pending["commit"]["committer"]["date"]) - age_seconds = max(0, int((now - oldest_time).total_seconds())) - if commit_count >= batch_size: - reason = f"batch threshold reached: {commit_count} commits" - dispatch = True - elif age_seconds >= max_wait_seconds: - reason = f"maximum wait reached: oldest pending commit is {age_seconds} seconds old" - dispatch = True - else: - reason = ( - f"deferred: {commit_count}/{batch_size} commits and oldest pending age " - f"{age_seconds}/{max_wait_seconds} seconds" - ) - dispatch = False - return { - "dispatch": dispatch, - "reason": reason, - "baseline_sha": baseline_sha, - "commit_count": commit_count, - } - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("--run-id", default="") - parser.add_argument("--batch-size", type=int, default=3) - parser.add_argument("--max-wait-seconds", type=int, default=7200) - args = parser.parse_args() - if args.run_id and not args.run_id.isdigit(): - parser.error("--run-id must be numeric") - if args.batch_size < 1 or args.max_wait_seconds < 1: - parser.error("batch size and maximum wait must be positive") - - token = os.environ.get("GITHUB_TOKEN") - if not token: - raise SystemExit("GITHUB_TOKEN is required") - api = GitHubAPI(token) - candidate = resolve_candidate(api, args.run_id) - if candidate is None: - print(json.dumps({"dispatch": False, "reason": "no eligible public WoA build"})) - return - - selection = select_batch( - api, - candidate, - args.batch_size, - args.max_wait_seconds, - datetime.datetime.now(datetime.timezone.utc), - ) - selection.update(candidate) - print(json.dumps(selection, separators=(",", ":"), sort_keys=True)) - - -if __name__ == "__main__": - try: - main() - except Exception as error: - print(f"error: {error}", file=sys.stderr) - raise From 4003b7a08cb5c579554d305ce5e1815479fea810 Mon Sep 17 00:00:00 2001 From: isVoid <13521008+isVoid@users.noreply.github.com> Date: Wed, 14 Oct 2026 20:53:17 +0000 Subject: [PATCH 08/13] Consolidate automatic WoA dispatch in woa-main-ci --- .github/workflows/ci.yml | 2 +- ...oa-main-automation.yml => woa-main-ci.yml} | 122 +++++++++++-- .github/workflows/woa-validation-dispatch.yml | 165 ------------------ 3 files changed, 113 insertions(+), 176 deletions(-) rename .github/workflows/{woa-main-automation.yml => woa-main-ci.yml} (51%) delete mode 100644 .github/workflows/woa-validation-dispatch.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d84ae54428..0d3b3561892 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -490,7 +490,7 @@ jobs: actions: read checks: read contents: read - uses: ./.github/workflows/woa-main-automation.yml + uses: ./.github/workflows/woa-main-ci.yml with: public_run_id: ${{ github.run_id }} secrets: inherit diff --git a/.github/workflows/woa-main-automation.yml b/.github/workflows/woa-main-ci.yml similarity index 51% rename from .github/workflows/woa-main-automation.yml rename to .github/workflows/woa-main-ci.yml index 6a68031f5ef..8e9874d65e0 100644 --- a/.github/workflows/woa-main-automation.yml +++ b/.github/workflows/woa-main-ci.yml @@ -2,7 +2,8 @@ # # SPDX-License-Identifier: Apache-2.0 -name: "WoA public-main automation" +name: "WoA main CI" +run-name: "WoA main CI for ${{ inputs.public_run_id || 'latest successful public main CI run' }}" on: workflow_call: @@ -18,7 +19,7 @@ on: workflow_dispatch: inputs: public_run_id: - description: Optional exact public CI run; empty selects the newest successful build + description: Eligible public main CI run; leave empty to find the latest successful main CI run required: false type: string @@ -135,15 +136,116 @@ jobs: echo "public_run_id=$run_id" >> "$GITHUB_OUTPUT" echo "$reason" >> "$GITHUB_STEP_SUMMARY" - dispatch: + resolve-public-build: needs: select - if: ${{ needs.select.outputs.dispatch == 'true' }} + if: ${{ github.repository == 'NVIDIA/cuda-python' && needs.select.outputs.dispatch == 'true' }} + runs-on: ubuntu-latest + timeout-minutes: 10 permissions: actions: read contents: read - uses: ./.github/workflows/woa-validation-dispatch.yml - with: - public_run_id: ${{ needs.select.outputs.public-run-id }} - baseline_sha: ${{ needs.select.outputs.baseline-sha }} - commit_count: ${{ needs.select.outputs.commit-count }} - secrets: inherit + outputs: + correlation-id: ${{ steps.resolve.outputs.correlation_id }} + public-run-id: ${{ steps.resolve.outputs.public_run_id }} + public-sha: ${{ steps.resolve.outputs.public_sha }} + steps: + - name: Resolve exact public build + id: resolve + env: + GH_TOKEN: ${{ github.token }} + PUBLIC_RUN_ID: ${{ needs.select.outputs.public-run-id }} + run: | + set -euo pipefail + [[ "$PUBLIC_RUN_ID" =~ ^[1-9][0-9]*$ ]] + run=$(gh api \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA/cuda-python/actions/runs/$PUBLIC_RUN_ID") + jq -e ' + .repository.full_name == "NVIDIA/cuda-python" and + .workflow_id == 155304118 and + .path == ".github/workflows/ci.yml" and + .event == "push" and + .head_branch == "main" and + ((.status == "in_progress" and .conclusion == null) or + (.status == "completed" and .conclusion == "success")) and + (.head_sha | test("^[0-9a-f]{40}$")) + ' <<< "$run" >/dev/null + public_sha=$(jq -r .head_sha <<< "$run") + + comparison=$(gh api \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA/cuda-python/compare/$public_sha...main") + jq -e '.status == "ahead" or .status == "identical"' <<< "$comparison" >/dev/null + + correlation_id="v1:NVIDIA/cuda-python:$PUBLIC_RUN_ID:$public_sha" + echo "correlation_id=$correlation_id" >> "$GITHUB_OUTPUT" + echo "public_run_id=$PUBLIC_RUN_ID" >> "$GITHUB_OUTPUT" + echo "public_sha=$public_sha" >> "$GITHUB_OUTPUT" + { + echo "Resolved public run \`$PUBLIC_RUN_ID\` at commit \`$public_sha\`." + } >> "$GITHUB_STEP_SUMMARY" + + dispatch-private-validation: + needs: + - select + - resolve-public-build + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: {} + steps: + - name: Create private dispatch token + id: private-app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 + with: + client-id: ${{ vars.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_CLIENT_ID }} + private-key: ${{ secrets.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_PRIVATE_KEY }} + owner: NVIDIA-dev + repositories: cuda-python-private + permission-actions: write + + - name: Dispatch and verify exact private validation + env: + BASELINE_SHA: ${{ needs.select.outputs.baseline-sha }} + COMMIT_COUNT: ${{ needs.select.outputs.commit-count }} + CORRELATION_ID: ${{ needs.resolve-public-build.outputs.correlation-id }} + GH_TOKEN: ${{ steps.private-app-token.outputs.token }} + PUBLIC_RUN_ID: ${{ needs.resolve-public-build.outputs.public-run-id }} + PUBLIC_SHA: ${{ needs.resolve-public-build.outputs.public-sha }} + run: | + set -euo pipefail + payload=$(jq -n \ + --arg baseline_sha "$BASELINE_SHA" \ + --arg commit_count "$COMMIT_COUNT" \ + --arg correlation_id "$CORRELATION_ID" \ + --arg run_id "$PUBLIC_RUN_ID" \ + --arg sha "$PUBLIC_SHA" \ + '{ + ref: "ctk-next", + return_run_details: true, + inputs: { + schema_version: "1", + public_repository: "NVIDIA/cuda-python", + public_workflow_id: "155304118", + public_run_id: $run_id, + public_sha: $sha, + baseline_sha: $baseline_sha, + commit_count: $commit_count, + correlation_id: $correlation_id + } + }') + response=$(gh api \ + --method POST \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + repos/NVIDIA-dev/cuda-python-private/actions/workflows/ctk-next-woa-validation.yml/dispatches \ + --input - <<< "$payload") + private_run_id=$(jq -er '.workflow_run_id | tostring' <<< "$response") + run=$(gh api \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA-dev/cuda-python-private/actions/runs/$private_run_id") + jq -e ' + .repository.full_name == "NVIDIA-dev/cuda-python-private" and + .path == ".github/workflows/ctk-next-woa-validation.yml" and + .event == "workflow_dispatch" and + .head_branch == "ctk-next" + ' <<< "$run" >/dev/null + echo "Private validation workflow accepted the exact dispatch." >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/woa-validation-dispatch.yml b/.github/workflows/woa-validation-dispatch.yml deleted file mode 100644 index a11a34b2f1e..00000000000 --- a/.github/workflows/woa-validation-dispatch.yml +++ /dev/null @@ -1,165 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# -# SPDX-License-Identifier: Apache-2.0 - -name: "WoA validation dispatch" -run-name: "WoA exact dispatch for ${{ inputs.public_run_id || 'latest successful public main CI run' }}" - -on: - workflow_call: - inputs: - public_run_id: - required: true - type: string - baseline_sha: - required: false - default: "" - type: string - commit_count: - required: false - default: "1" - type: string - secrets: - CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_PRIVATE_KEY: - required: true - workflow_dispatch: - inputs: - public_run_id: - description: Eligible public main CI run; leave empty to find the latest successful main CI run - required: false - type: string - -permissions: {} - -jobs: - resolve-public-build: - if: ${{ github.repository == 'NVIDIA/cuda-python' }} - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - actions: read - contents: read - outputs: - correlation-id: ${{ steps.resolve.outputs.correlation_id }} - public-run-id: ${{ steps.resolve.outputs.public_run_id }} - public-sha: ${{ steps.resolve.outputs.public_sha }} - steps: - - name: Checkout main run lookup helper - if: ${{ inputs.public_run_id == '' }} - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: main - persist-credentials: false - sparse-checkout: ci/tools/lookup-run-id - sparse-checkout-cone-mode: false - - - name: Resolve exact public build - id: resolve - env: - GH_TOKEN: ${{ github.token }} - PUBLIC_RUN_ID: ${{ inputs.public_run_id }} - run: | - set -euo pipefail - public_sha='' - if [[ -z "$PUBLIC_RUN_ID" ]]; then - lookup_output=$(./ci/tools/lookup-run-id --branch main --head-sha NVIDIA/cuda-python CI) - mapfile -t lookup_result <<< "$lookup_output" - [[ ${#lookup_result[@]} == 2 ]] - PUBLIC_RUN_ID=${lookup_result[0]} - public_sha=${lookup_result[1]} - [[ "$public_sha" =~ ^[0-9a-f]{40}$ ]] - fi - [[ "$PUBLIC_RUN_ID" =~ ^[1-9][0-9]*$ ]] - run=$(gh api \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - "repos/NVIDIA/cuda-python/actions/runs/$PUBLIC_RUN_ID") - jq -e --arg sha "$public_sha" ' - .repository.full_name == "NVIDIA/cuda-python" and - .workflow_id == 155304118 and - .path == ".github/workflows/ci.yml" and - .event == "push" and - .head_branch == "main" and - ((.status == "in_progress" and .conclusion == null) or - (.status == "completed" and .conclusion == "success")) and - ($sha == "" or .head_sha == $sha) and - (.head_sha | test("^[0-9a-f]{40}$")) - ' <<< "$run" >/dev/null - if [[ -z "$public_sha" ]]; then - public_sha=$(jq -r .head_sha <<< "$run") - fi - - comparison=$(gh api \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - "repos/NVIDIA/cuda-python/compare/$public_sha...main") - jq -e '.status == "ahead" or .status == "identical"' <<< "$comparison" >/dev/null - - correlation_id="v1:NVIDIA/cuda-python:$PUBLIC_RUN_ID:$public_sha" - echo "correlation_id=$correlation_id" >> "$GITHUB_OUTPUT" - echo "public_run_id=$PUBLIC_RUN_ID" >> "$GITHUB_OUTPUT" - echo "public_sha=$public_sha" >> "$GITHUB_OUTPUT" - { - echo "Resolved public run \`$PUBLIC_RUN_ID\` at commit \`$public_sha\`." - } >> "$GITHUB_STEP_SUMMARY" - - dispatch-private-validation: - needs: resolve-public-build - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: {} - steps: - - name: Create private dispatch token - id: private-app-token - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 - with: - client-id: ${{ vars.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_CLIENT_ID }} - private-key: ${{ secrets.CUDA_PYTHON_WOA_XREPO_CI_PRIVATE_APP_PRIVATE_KEY }} - owner: NVIDIA-dev - repositories: cuda-python-private - permission-actions: write - - - name: Dispatch and verify exact private validation - env: - BASELINE_SHA: ${{ inputs.baseline_sha || '' }} - COMMIT_COUNT: ${{ inputs.commit_count || '1' }} - CORRELATION_ID: ${{ needs.resolve-public-build.outputs.correlation-id }} - GH_TOKEN: ${{ steps.private-app-token.outputs.token }} - PUBLIC_RUN_ID: ${{ needs.resolve-public-build.outputs.public-run-id }} - PUBLIC_SHA: ${{ needs.resolve-public-build.outputs.public-sha }} - run: | - set -euo pipefail - payload=$(jq -n \ - --arg baseline_sha "$BASELINE_SHA" \ - --arg commit_count "$COMMIT_COUNT" \ - --arg correlation_id "$CORRELATION_ID" \ - --arg run_id "$PUBLIC_RUN_ID" \ - --arg sha "$PUBLIC_SHA" \ - '{ - ref: "ctk-next", - return_run_details: true, - inputs: { - schema_version: "1", - public_repository: "NVIDIA/cuda-python", - public_workflow_id: "155304118", - public_run_id: $run_id, - public_sha: $sha, - baseline_sha: $baseline_sha, - commit_count: $commit_count, - correlation_id: $correlation_id - } - }') - response=$(gh api \ - --method POST \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - repos/NVIDIA-dev/cuda-python-private/actions/workflows/ctk-next-woa-validation.yml/dispatches \ - --input - <<< "$payload") - private_run_id=$(jq -er '.workflow_run_id | tostring' <<< "$response") - run=$(gh api \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - "repos/NVIDIA-dev/cuda-python-private/actions/runs/$private_run_id") - jq -e ' - .repository.full_name == "NVIDIA-dev/cuda-python-private" and - .path == ".github/workflows/ctk-next-woa-validation.yml" and - .event == "workflow_dispatch" and - .head_branch == "ctk-next" - ' <<< "$run" >/dev/null - echo "Private validation workflow accepted the exact dispatch." >> "$GITHUB_STEP_SUMMARY" From 261a0ee877d0d6dcbca6860849a361d3e80ef11b Mon Sep 17 00:00:00 2001 From: isVoid <13521008+isVoid@users.noreply.github.com> Date: Wed, 14 Oct 2026 20:57:29 +0000 Subject: [PATCH 09/13] Dispatch the renamed private WoA main CI workflow --- .github/workflows/woa-main-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/woa-main-ci.yml b/.github/workflows/woa-main-ci.yml index 8e9874d65e0..27e20aa6c1e 100644 --- a/.github/workflows/woa-main-ci.yml +++ b/.github/workflows/woa-main-ci.yml @@ -236,7 +236,7 @@ jobs: response=$(gh api \ --method POST \ -H 'X-GitHub-Api-Version: 2026-03-10' \ - repos/NVIDIA-dev/cuda-python-private/actions/workflows/ctk-next-woa-validation.yml/dispatches \ + repos/NVIDIA-dev/cuda-python-private/actions/workflows/ctk-next-woa-main-ci.yml/dispatches \ --input - <<< "$payload") private_run_id=$(jq -er '.workflow_run_id | tostring' <<< "$response") run=$(gh api \ @@ -244,7 +244,7 @@ jobs: "repos/NVIDIA-dev/cuda-python-private/actions/runs/$private_run_id") jq -e ' .repository.full_name == "NVIDIA-dev/cuda-python-private" and - .path == ".github/workflows/ctk-next-woa-validation.yml" and + .path == ".github/workflows/ctk-next-woa-main-ci.yml" and .event == "workflow_dispatch" and .head_branch == "ctk-next" ' <<< "$run" >/dev/null From 1efa012fc4656f8ed7d35b7620a91e05cce86c2a Mon Sep 17 00:00:00 2001 From: isVoid <13521008+isVoid@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:05:30 +0000 Subject: [PATCH 10/13] Keep WoA CI close to the manual broker and filter lookup events --- .github/workflows/woa-main-ci.yml | 153 +++++++-------------------- ci/tools/lookup-run-id | 18 +++- ci/tools/select-woa-batch | 67 ++++++++++++ ci/tools/tests/test_lookup_run_id.py | 121 +++++++++++++++++++++ 4 files changed, 244 insertions(+), 115 deletions(-) create mode 100644 ci/tools/select-woa-batch create mode 100644 ci/tools/tests/test_lookup_run_id.py diff --git a/.github/workflows/woa-main-ci.yml b/.github/workflows/woa-main-ci.yml index 27e20aa6c1e..8ac5c7ad3a4 100644 --- a/.github/workflows/woa-main-ci.yml +++ b/.github/workflows/woa-main-ci.yml @@ -26,14 +26,14 @@ on: permissions: {} jobs: - select: + resolve-public-build: if: ${{ github.repository == 'NVIDIA/cuda-python' }} runs-on: ubuntu-latest timeout-minutes: 10 permissions: actions: read - checks: read contents: read + checks: read concurrency: group: cuda-python-woa-public-selector cancel-in-progress: false @@ -41,126 +41,41 @@ jobs: baseline-sha: ${{ steps.select.outputs.baseline_sha }} commit-count: ${{ steps.select.outputs.commit_count }} dispatch: ${{ steps.select.outputs.dispatch }} - public-run-id: ${{ steps.select.outputs.public_run_id }} - steps: - - name: Select public batch - id: select - env: - GH_TOKEN: ${{ github.token }} - INPUT_RUN_ID: ${{ inputs.public_run_id }} - run: | - set -euo pipefail - if [[ -n "$INPUT_RUN_ID" ]]; then - run=$(gh api \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - "repos/NVIDIA/cuda-python/actions/runs/$INPUT_RUN_ID") - else - run=$(gh api --method GET \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - repos/NVIDIA/cuda-python/actions/workflows/ci.yml/runs \ - -f branch=main -f event=push -f status=success -F per_page=1 \ - --jq '.workflow_runs[0]') - fi - jq -e ' - .repository.full_name == "NVIDIA/cuda-python" and - .workflow_id == 155304118 and - .path == ".github/workflows/ci.yml" and - .event == "push" and - .head_branch == "main" and - ((.status == "in_progress" and .conclusion == null) or - (.status == "completed" and .conclusion == "success")) - ' <<< "$run" >/dev/null - run_id=$(jq -r '.id | tostring' <<< "$run") - sha=$(jq -r .head_sha <<< "$run") - external_id="woa:v1:NVIDIA/cuda-python:$run_id:$sha" - - checks=$(gh api \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - "repos/NVIDIA/cuda-python/commits/$sha/check-runs?check_name=cuda-python%20WoA%20integration&per_page=100") - if jq -e --arg id "$external_id" ' - any(.check_runs[]; .app.id == 4954254 and .external_id == $id) - ' <<< "$checks" >/dev/null; then - echo "dispatch=false" >> "$GITHUB_OUTPUT" - echo "This exact public run already has an App-owned attempt." >> "$GITHUB_STEP_SUMMARY" - exit 0 - fi - - commits=$(gh api --method GET \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - repos/NVIDIA/cuda-python/commits -f sha=main -F per_page=100) - candidate_index=$(jq -er --arg sha "$sha" ' - [to_entries[] | select(.value.sha == $sha) | .key][0] - ' <<< "$commits") - commit_total=$(jq length <<< "$commits") - baseline_index="" - baseline_sha="" - for ((index = candidate_index + 1; index < commit_total; index++)); do - older_sha=$(jq -r --argjson index "$index" '.[$index].sha' <<< "$commits") - older_checks=$(gh api \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - "repos/NVIDIA/cuda-python/commits/$older_sha/check-runs?check_name=cuda-python%20WoA%20integration&per_page=100") - if jq -e ' - any(.check_runs[]; - .app.id == 4954254 and - .status == "completed" and - .conclusion == "success") - ' <<< "$older_checks" >/dev/null; then - baseline_index=$index - baseline_sha=$older_sha - break - fi - done - - dispatch=true - commit_count=1 - reason="Bootstrap: no successful App-owned baseline exists in the latest 100 commits." - if [[ -n "$baseline_index" ]]; then - commit_count=$((baseline_index - candidate_index)) - oldest_pending_index=$((baseline_index - 1)) - oldest_pending_at=$(jq -r \ - --argjson index "$oldest_pending_index" \ - '.[$index].commit.committer.date' <<< "$commits") - age_seconds=$(($(date +%s) - $(date -d "$oldest_pending_at" +%s))) - if (( commit_count >= 3 )); then - reason="Batch threshold reached: $commit_count commits." - elif (( age_seconds >= 7200 )); then - reason="Two-hour fallback reached: oldest pending commit is $age_seconds seconds old." - else - dispatch=false - reason="Deferred: $commit_count/3 commits and oldest pending age $age_seconds/7200 seconds." - fi - fi - echo "baseline_sha=$baseline_sha" >> "$GITHUB_OUTPUT" - echo "commit_count=$commit_count" >> "$GITHUB_OUTPUT" - echo "dispatch=$dispatch" >> "$GITHUB_OUTPUT" - echo "public_run_id=$run_id" >> "$GITHUB_OUTPUT" - echo "$reason" >> "$GITHUB_STEP_SUMMARY" - - resolve-public-build: - needs: select - if: ${{ github.repository == 'NVIDIA/cuda-python' && needs.select.outputs.dispatch == 'true' }} - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - actions: read - contents: read - outputs: correlation-id: ${{ steps.resolve.outputs.correlation_id }} public-run-id: ${{ steps.resolve.outputs.public_run_id }} public-sha: ${{ steps.resolve.outputs.public_sha }} steps: + - name: Checkout CI selection helpers + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.sha }} + persist-credentials: false + sparse-checkout: | + ci/tools/lookup-run-id + ci/tools/select-woa-batch + sparse-checkout-cone-mode: false + - name: Resolve exact public build id: resolve env: GH_TOKEN: ${{ github.token }} - PUBLIC_RUN_ID: ${{ needs.select.outputs.public-run-id }} + PUBLIC_RUN_ID: ${{ inputs.public_run_id }} run: | set -euo pipefail + public_sha='' + if [[ -z "$PUBLIC_RUN_ID" ]]; then + lookup_output=$(./ci/tools/lookup-run-id --branch main --event push --head-sha NVIDIA/cuda-python CI) + mapfile -t lookup_result <<< "$lookup_output" + [[ ${#lookup_result[@]} == 2 ]] + PUBLIC_RUN_ID=${lookup_result[0]} + public_sha=${lookup_result[1]} + [[ "$public_sha" =~ ^[0-9a-f]{40}$ ]] + fi [[ "$PUBLIC_RUN_ID" =~ ^[1-9][0-9]*$ ]] run=$(gh api \ -H 'X-GitHub-Api-Version: 2026-03-10' \ "repos/NVIDIA/cuda-python/actions/runs/$PUBLIC_RUN_ID") - jq -e ' + jq -e --arg sha "$public_sha" ' .repository.full_name == "NVIDIA/cuda-python" and .workflow_id == 155304118 and .path == ".github/workflows/ci.yml" and @@ -168,9 +83,12 @@ jobs: .head_branch == "main" and ((.status == "in_progress" and .conclusion == null) or (.status == "completed" and .conclusion == "success")) and + ($sha == "" or .head_sha == $sha) and (.head_sha | test("^[0-9a-f]{40}$")) ' <<< "$run" >/dev/null - public_sha=$(jq -r .head_sha <<< "$run") + if [[ -z "$public_sha" ]]; then + public_sha=$(jq -r .head_sha <<< "$run") + fi comparison=$(gh api \ -H 'X-GitHub-Api-Version: 2026-03-10' \ @@ -185,10 +103,17 @@ jobs: echo "Resolved public run \`$PUBLIC_RUN_ID\` at commit \`$public_sha\`." } >> "$GITHUB_STEP_SUMMARY" + - name: Select public batch + id: select + env: + GH_TOKEN: ${{ github.token }} + PUBLIC_RUN_ID: ${{ steps.resolve.outputs.public_run_id }} + PUBLIC_SHA: ${{ steps.resolve.outputs.public_sha }} + run: bash ./ci/tools/select-woa-batch + dispatch-private-validation: - needs: - - select - - resolve-public-build + needs: resolve-public-build + if: ${{ needs.resolve-public-build.outputs.dispatch == 'true' }} runs-on: ubuntu-latest timeout-minutes: 10 permissions: {} @@ -205,8 +130,8 @@ jobs: - name: Dispatch and verify exact private validation env: - BASELINE_SHA: ${{ needs.select.outputs.baseline-sha }} - COMMIT_COUNT: ${{ needs.select.outputs.commit-count }} + BASELINE_SHA: ${{ needs.resolve-public-build.outputs.baseline-sha }} + COMMIT_COUNT: ${{ needs.resolve-public-build.outputs.commit-count }} CORRELATION_ID: ${{ needs.resolve-public-build.outputs.correlation-id }} GH_TOKEN: ${{ steps.private-app-token.outputs.token }} PUBLIC_RUN_ID: ${{ needs.resolve-public-build.outputs.public-run-id }} diff --git a/ci/tools/lookup-run-id b/ci/tools/lookup-run-id index 89833cfe1bd..4f7eb2c043f 100755 --- a/ci/tools/lookup-run-id +++ b/ci/tools/lookup-run-id @@ -24,6 +24,7 @@ Usage: Options: --tag Find run by git tag (requires local git repo with the tag) --branch Find latest successful run on the given branch + --event Restrict branch-mode runs to a trigger event (for example push) --artifact Require an unexpired artifact matching this shell glob (repeatable; branch mode only) --head-sha Also print the run's head commit SHA (second line) @@ -33,6 +34,7 @@ Examples: $0 --branch main NVIDIA/cuda-python $0 --branch 12.9.x --artifact 'cuda-bindings-python313-*' NVIDIA/cuda-python $0 --branch main --head-sha NVIDIA/cuda-python "CI" + $0 --branch main --event push --head-sha NVIDIA/cuda-python "CI" EOF exit 1 } @@ -41,6 +43,7 @@ EOF MODE="" REF="" HEAD_SHA_FLAG=0 +EVENT="" ARTIFACT_PATTERNS=() while [[ $# -gt 0 ]]; do @@ -56,6 +59,9 @@ while [[ $# -gt 0 ]]; do --artifact) [[ $# -ge 2 ]] || usage ARTIFACT_PATTERNS+=("${2}"); shift 2 ;; + --event) + [[ $# -ge 2 && -n "${2}" ]] || usage + EVENT="${2}"; shift 2 ;; --head-sha) HEAD_SHA_FLAG=1; shift ;; -h|--help) @@ -83,6 +89,11 @@ if [[ "${MODE}" != "branch" && ${#ARTIFACT_PATTERNS[@]} -gt 0 ]]; then exit 1 fi +if [[ "${MODE}" != "branch" && -n "${EVENT}" ]]; then + echo "Error: --event is only supported with --branch" >&2 + exit 1 +fi + # ── Prerequisite checks ── if [[ -z "${GH_TOKEN:-}" ]]; then echo "Error: GH_TOKEN environment variable is required" >&2 @@ -176,7 +187,12 @@ if [[ "${MODE}" == "branch" ]]; then "repos/${REPOSITORY}/actions/workflows/${WORKFLOW_ID}/runs" \ -f "head_sha=${commit_sha}" \ -F "per_page=100" \ - --jq '[.workflow_runs[] | select(.conclusion == "success") | {id, head_sha, created_at}] | sort_by(.created_at) | reverse'); then + | jq -c --arg event "${EVENT}" ' + [.workflow_runs[] + | select(.conclusion == "success" and ($event == "" or .event == $event)) + | {id, head_sha, created_at}] + | sort_by(.created_at) | reverse + '); then echo "Error: Failed to list runs for commit ${commit_sha}" >&2 exit 1 fi diff --git a/ci/tools/select-woa-batch b/ci/tools/select-woa-batch new file mode 100644 index 00000000000..f88dce023d6 --- /dev/null +++ b/ci/tools/select-woa-batch @@ -0,0 +1,67 @@ +#!/usr/bin/env bash + +set -euo pipefail +run_id=$PUBLIC_RUN_ID +sha=$PUBLIC_SHA +external_id="woa:v1:NVIDIA/cuda-python:$run_id:$sha" + +checks=$(gh api \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA/cuda-python/commits/$sha/check-runs?check_name=cuda-python%20WoA%20integration&per_page=100") +if jq -e --arg id "$external_id" ' + any(.check_runs[]; .app.id == 4954254 and .external_id == $id) +' <<< "$checks" >/dev/null; then + echo "dispatch=false" >> "$GITHUB_OUTPUT" + echo "This exact public run already has an App-owned attempt." >> "$GITHUB_STEP_SUMMARY" + exit 0 +fi + +commits=$(gh api --method GET \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + repos/NVIDIA/cuda-python/commits -f sha=main -F per_page=100) +candidate_index=$(jq -er --arg sha "$sha" ' + [to_entries[] | select(.value.sha == $sha) | .key][0] +' <<< "$commits") +commit_total=$(jq length <<< "$commits") +baseline_index="" +baseline_sha="" +for ((index = candidate_index + 1; index < commit_total; index++)); do + older_sha=$(jq -r --argjson index "$index" '.[$index].sha' <<< "$commits") + older_checks=$(gh api \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "repos/NVIDIA/cuda-python/commits/$older_sha/check-runs?check_name=cuda-python%20WoA%20integration&per_page=100") + if jq -e ' + any(.check_runs[]; + .app.id == 4954254 and + .status == "completed" and + .conclusion == "success") + ' <<< "$older_checks" >/dev/null; then + baseline_index=$index + baseline_sha=$older_sha + break + fi +done + +dispatch=true +commit_count=1 +reason="Bootstrap: no successful App-owned baseline exists in the latest 100 commits." +if [[ -n "$baseline_index" ]]; then + commit_count=$((baseline_index - candidate_index)) + oldest_pending_index=$((baseline_index - 1)) + oldest_pending_at=$(jq -r \ + --argjson index "$oldest_pending_index" \ + '.[$index].commit.committer.date' <<< "$commits") + age_seconds=$(($(date +%s) - $(date -d "$oldest_pending_at" +%s))) + if (( commit_count >= 3 )); then + reason="Batch threshold reached: $commit_count commits." + elif (( age_seconds >= 7200 )); then + reason="Two-hour fallback reached: oldest pending commit is $age_seconds seconds old." + else + dispatch=false + reason="Deferred: $commit_count/3 commits and oldest pending age $age_seconds/7200 seconds." + fi +fi +echo "baseline_sha=$baseline_sha" >> "$GITHUB_OUTPUT" +echo "commit_count=$commit_count" >> "$GITHUB_OUTPUT" +echo "dispatch=$dispatch" >> "$GITHUB_OUTPUT" +echo "$reason" >> "$GITHUB_STEP_SUMMARY" diff --git a/ci/tools/tests/test_lookup_run_id.py b/ci/tools/tests/test_lookup_run_id.py new file mode 100644 index 00000000000..e2c08c9df10 --- /dev/null +++ b/ci/tools/tests/test_lookup_run_id.py @@ -0,0 +1,121 @@ +# ruff: noqa: S603 + +import json +import os +import subprocess +import sys +from pathlib import Path + +import pytest + +LOOKUP_RUN_ID = Path(__file__).resolve().parents[1] / "lookup-run-id" +HEAD_SHA = "a" * 40 +OLDER_SHA = "b" * 40 + + +@pytest.fixture +def lookup(tmp_path): + fixture_path = tmp_path / "fixture.json" + mock = tmp_path / "gh" + mock.write_text( + f"#!{sys.executable}\n" + "import json, os, sys\n" + "from pathlib import Path\n" + "fixture = json.loads(Path(os.environ['LOOKUP_FIXTURE']).read_text())\n" + "arguments = sys.argv[1:]\n" + "endpoint = next(value for value in arguments if value.startswith('repos/'))\n" + "if endpoint.endswith('/actions/workflows/ci.yml'):\n" + " print(155304118)\n" + "elif '/branches/' in endpoint:\n" + " print(fixture['commits'][0])\n" + "elif endpoint.endswith('/commits'):\n" + " print('\\n'.join(fixture['commits']))\n" + "elif endpoint.endswith('/runs'):\n" + " sha = next(value.split('=', 1)[1] for value in arguments if value.startswith('head_sha='))\n" + " print(json.dumps({'workflow_runs': fixture['runs'].get(sha, [])}))\n" + "else:\n" + " raise RuntimeError(endpoint)\n" + ) + mock.chmod(0o755) + + def invoke(runs, *, event=None, commits=None, mode=None): + fixture_path.write_text(json.dumps({"commits": commits or [HEAD_SHA], "runs": runs})) + arguments = ["bash", str(LOOKUP_RUN_ID), *(mode or ["--branch", "main"]), "--head-sha"] + if event is not None: + arguments.extend(["--event", event]) + arguments.extend(["NVIDIA/cuda-python", "ci.yml"]) + return subprocess.run( + arguments, + env=dict( + os.environ, + PATH=str(tmp_path) + os.pathsep + os.environ["PATH"], + GH_TOKEN=tmp_path.name, + LOOKUP_FIXTURE=str(fixture_path), + LOOKUP_RUN_ID_COMMIT_SCAN_LIMIT="2", + ), + capture_output=True, + text=True, + timeout=15, + ) + + return invoke + + +def make_run(run_id, event, *, sha=HEAD_SHA, conclusion="success"): + return { + "id": run_id, + "head_sha": sha, + "created_at": f"2026-10-14T00:00:{run_id:02d}Z", + "event": event, + "conclusion": conclusion, + } + + +@pytest.mark.agent_authored(model="gpt-6") +@pytest.mark.parametrize("event,expected", [(None, 30), ("push", 20), ("workflow_dispatch", 10)]) +def test_optional_event_filter_selects_latest_matching_successful_run(lookup, event, expected): + result = lookup( + {HEAD_SHA: [make_run(30, "schedule"), make_run(20, "push"), make_run(10, "workflow_dispatch")]}, + event=event, + ) + assert result.returncode == 0, result.stderr + assert result.stdout.splitlines() == [str(expected), HEAD_SHA] + + +@pytest.mark.agent_authored(model="gpt-6") +def test_push_lookup_continues_to_older_commit_after_scheduled_head_run(lookup): + result = lookup( + { + HEAD_SHA: [make_run(30, "schedule")], + OLDER_SHA: [make_run(20, "push", sha=OLDER_SHA)], + }, + event="push", + commits=[HEAD_SHA, OLDER_SHA], + ) + assert result.returncode == 0, result.stderr + assert result.stdout.splitlines() == ["20", OLDER_SHA] + + +@pytest.mark.agent_authored(model="gpt-6") +def test_push_lookup_rejects_failed_push_even_when_scheduled_run_succeeds(lookup): + result = lookup( + {HEAD_SHA: [make_run(30, "schedule"), make_run(20, "push", conclusion="failure")]}, + event="push", + ) + assert result.returncode != 0 + assert result.stdout == "" + assert "No successful" in result.stderr + + +@pytest.mark.agent_authored(model="gpt-6") +def test_event_filter_is_rejected_in_tag_mode(lookup): + result = lookup({}, event="push", mode=["--tag", "v1.0.0"]) + assert result.returncode != 0 + assert "--event is only supported with --branch" in result.stderr + + +@pytest.mark.agent_authored(model="gpt-6") +def test_empty_event_is_rejected(lookup): + result = lookup({}, event="") + assert result.returncode != 0 + assert "Usage:" in result.stderr From 18b33a2052097339d6cd62aec059f7655c83d7ca Mon Sep 17 00:00:00 2001 From: isVoid <13521008+isVoid@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:52:36 +0000 Subject: [PATCH 11/13] Serialize public WoA selection and dispatch together --- .github/workflows/woa-main-ci.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/woa-main-ci.yml b/.github/workflows/woa-main-ci.yml index 8ac5c7ad3a4..0add13654f3 100644 --- a/.github/workflows/woa-main-ci.yml +++ b/.github/workflows/woa-main-ci.yml @@ -25,6 +25,10 @@ on: permissions: {} +concurrency: + group: cuda-python-woa-public-selector + cancel-in-progress: false + jobs: resolve-public-build: if: ${{ github.repository == 'NVIDIA/cuda-python' }} @@ -34,9 +38,6 @@ jobs: actions: read contents: read checks: read - concurrency: - group: cuda-python-woa-public-selector - cancel-in-progress: false outputs: baseline-sha: ${{ steps.select.outputs.baseline_sha }} commit-count: ${{ steps.select.outputs.commit_count }} From 4d5194227064d7f59dd014cb8f89a91e8c63f6bd Mon Sep 17 00:00:00 2001 From: isVoid <13521008+isVoid@users.noreply.github.com> Date: Wed, 14 Oct 2026 23:02:01 +0000 Subject: [PATCH 12/13] Explain WoA batch selection and retry policy --- ci/tools/select-woa-batch | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ci/tools/select-woa-batch b/ci/tools/select-woa-batch index f88dce023d6..297c971f3f0 100644 --- a/ci/tools/select-woa-batch +++ b/ci/tools/select-woa-batch @@ -1,10 +1,13 @@ #!/usr/bin/env bash set -euo pipefail +# The resolver has already validated this public main CI run and commit. run_id=$PUBLIC_RUN_ID sha=$PUBLIC_SHA external_id="woa:v1:NVIDIA/cuda-python:$run_id:$sha" +# Any matching check from the reporting App suppresses a duplicate dispatch, +# including checks that are still running, failed, or cancelled. checks=$(gh api \ -H 'X-GitHub-Api-Version: 2026-03-10' \ "repos/NVIDIA/cuda-python/commits/$sha/check-runs?check_name=cuda-python%20WoA%20integration&per_page=100") @@ -16,6 +19,7 @@ if jq -e --arg id "$external_id" ' exit 0 fi +# Scan at most 100 main commits, newest first. The candidate must be in this window. commits=$(gh api --method GET \ -H 'X-GitHub-Api-Version: 2026-03-10' \ repos/NVIDIA/cuda-python/commits -f sha=main -F per_page=100) @@ -25,6 +29,8 @@ candidate_index=$(jq -er --arg sha "$sha" ' commit_total=$(jq length <<< "$commits") baseline_index="" baseline_sha="" +# The nearest older successful App-owned WoA check is the batching baseline. +# Failed or cancelled validations do not advance it. for ((index = candidate_index + 1; index < commit_total; index++)); do older_sha=$(jq -r --argjson index "$index" '.[$index].sha' <<< "$commits") older_checks=$(gh api \ @@ -42,16 +48,21 @@ for ((index = candidate_index + 1; index < commit_total; index++)); do fi done +# Without a baseline in the scan window, validate immediately to bootstrap. +# The initial count of one is a reporting default, not a measured history count. dispatch=true commit_count=1 reason="Bootstrap: no successful App-owned baseline exists in the latest 100 commits." if [[ -n "$baseline_index" ]]; then + # Count commits after the baseline through the candidate; test only the candidate. commit_count=$((baseline_index - candidate_index)) oldest_pending_index=$((baseline_index - 1)) oldest_pending_at=$(jq -r \ --argjson index "$oldest_pending_index" \ '.[$index].commit.committer.date' <<< "$commits") + # Age starts at the oldest pending commit's timestamp, not workflow start time. age_seconds=$(($(date +%s) - $(date -d "$oldest_pending_at" +%s))) + # Dispatch after three commits or two hours; hourly cron revisits deferred batches. if (( commit_count >= 3 )); then reason="Batch threshold reached: $commit_count commits." elif (( age_seconds >= 7200 )); then @@ -61,6 +72,7 @@ if [[ -n "$baseline_index" ]]; then reason="Deferred: $commit_count/3 commits and oldest pending age $age_seconds/7200 seconds." fi fi +# Only dispatch=true enables the separate credential-bearing dispatch job. echo "baseline_sha=$baseline_sha" >> "$GITHUB_OUTPUT" echo "commit_count=$commit_count" >> "$GITHUB_OUTPUT" echo "dispatch=$dispatch" >> "$GITHUB_OUTPUT" From b085e6cdfa8db36296a71cdab747a7c0f07f1ec2 Mon Sep 17 00:00:00 2001 From: isVoid <13521008+isVoid@users.noreply.github.com> Date: Wed, 14 Oct 2026 23:05:46 +0000 Subject: [PATCH 13/13] Keep WoA batching metadata on the public side --- .github/workflows/woa-main-ci.yml | 8 -------- ci/tools/select-woa-batch | 7 +++---- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/.github/workflows/woa-main-ci.yml b/.github/workflows/woa-main-ci.yml index 0add13654f3..2c044f072d1 100644 --- a/.github/workflows/woa-main-ci.yml +++ b/.github/workflows/woa-main-ci.yml @@ -39,8 +39,6 @@ jobs: contents: read checks: read outputs: - baseline-sha: ${{ steps.select.outputs.baseline_sha }} - commit-count: ${{ steps.select.outputs.commit_count }} dispatch: ${{ steps.select.outputs.dispatch }} correlation-id: ${{ steps.resolve.outputs.correlation_id }} public-run-id: ${{ steps.resolve.outputs.public_run_id }} @@ -131,8 +129,6 @@ jobs: - name: Dispatch and verify exact private validation env: - BASELINE_SHA: ${{ needs.resolve-public-build.outputs.baseline-sha }} - COMMIT_COUNT: ${{ needs.resolve-public-build.outputs.commit-count }} CORRELATION_ID: ${{ needs.resolve-public-build.outputs.correlation-id }} GH_TOKEN: ${{ steps.private-app-token.outputs.token }} PUBLIC_RUN_ID: ${{ needs.resolve-public-build.outputs.public-run-id }} @@ -140,8 +136,6 @@ jobs: run: | set -euo pipefail payload=$(jq -n \ - --arg baseline_sha "$BASELINE_SHA" \ - --arg commit_count "$COMMIT_COUNT" \ --arg correlation_id "$CORRELATION_ID" \ --arg run_id "$PUBLIC_RUN_ID" \ --arg sha "$PUBLIC_SHA" \ @@ -154,8 +148,6 @@ jobs: public_workflow_id: "155304118", public_run_id: $run_id, public_sha: $sha, - baseline_sha: $baseline_sha, - commit_count: $commit_count, correlation_id: $correlation_id } }') diff --git a/ci/tools/select-woa-batch b/ci/tools/select-woa-batch index 297c971f3f0..91be1836d81 100644 --- a/ci/tools/select-woa-batch +++ b/ci/tools/select-woa-batch @@ -49,9 +49,7 @@ for ((index = candidate_index + 1; index < commit_total; index++)); do done # Without a baseline in the scan window, validate immediately to bootstrap. -# The initial count of one is a reporting default, not a measured history count. dispatch=true -commit_count=1 reason="Bootstrap: no successful App-owned baseline exists in the latest 100 commits." if [[ -n "$baseline_index" ]]; then # Count commits after the baseline through the candidate; test only the candidate. @@ -73,7 +71,8 @@ if [[ -n "$baseline_index" ]]; then fi fi # Only dispatch=true enables the separate credential-bearing dispatch job. -echo "baseline_sha=$baseline_sha" >> "$GITHUB_OUTPUT" -echo "commit_count=$commit_count" >> "$GITHUB_OUTPUT" echo "dispatch=$dispatch" >> "$GITHUB_OUTPUT" +if [[ -n "$baseline_sha" ]]; then + echo "Baseline: $baseline_sha; pending commits through $sha: $commit_count." >> "$GITHUB_STEP_SUMMARY" +fi echo "$reason" >> "$GITHUB_STEP_SUMMARY"