Skip to content

fix: overlap Kind cluster creation with host Go builds in E2E #5103

fix: overlap Kind cluster creation with host Go builds in E2E

fix: overlap Kind cluster creation with host Go builds in E2E #5103

Workflow file for this run

# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Aggregate merge gate — the ONLY required status check in the main branch
# ruleset. Every PR-gating CI check is either run or explicitly skipped here,
# and the final "gate" job aggregates all results into a single pass/fail.
#
# To add a new gating check:
# 1. Add a real job + companion -skip job with inverse if: conditions
# 2. Add both job names to the gate job's needs: list
# 3. No ruleset change needed — "gate" is the only required check.
#
# GPU tests are intentionally excluded (advisory only) until flake rate is
# assessed. They can be added later with the same real/skip pattern.
name: Merge Gate
on:
pull_request:
branches:
- main
workflow_dispatch: {}
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
# ---------------------------------------------------------------------------
# Path classification — determines which checks run vs skip
# ---------------------------------------------------------------------------
check-paths:
runs-on: ubuntu-latest
timeout-minutes: 2
outputs:
code: ${{ steps.code.outputs.code }}
actions: ${{ steps.changes.outputs.actions }}
deps: ${{ steps.changes.outputs.deps }}
renovate: ${{ steps.changes.outputs.renovate }}
bom: ${{ steps.changes.outputs.bom }}
tuning: ${{ steps.changes.outputs.tuning }}
notices: ${{ steps.changes.outputs.notices }}
coverage: ${{ steps.changes.outputs.coverage }}
docs: ${{ steps.changes.outputs.docs }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# Detect targeted path categories (default: any match = true)
- uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
id: changes
with:
filters: |
actions:
- '.github/workflows/**'
- '.github/actions/**'
deps:
# Inputs to the verify-licenses gate, which runs `make
# license-check` over the vendored dependency graph. Watch the
# dependency manifests and the vendor tree, plus every input that
# defines the check itself, so a change to the gate cannot skip
# its own gate: the Makefile recipe is the check's contract, the
# install-go-licenses action supplies the binary it runs (that
# action clears GOFLAGS, without which -trimpath strips GOROOT and
# the check passes while inspecting zero packages), and this
# workflow defines the job. The entry replaced here named
# '.github/workflows/verify-licenses.yaml', which was deleted in
# 3c9f6ec5 when the job moved into this workflow, so the filter
# had stopped watching anything the job actually consumes.
#
# The toolchain pins are watched too, because the job resolves
# them through load-versions before running the check: .go-version
# supplies the Go toolchain (which determines the `go list std`
# ignore set) and .settings.yaml supplies linting.go_licenses (the
# go-licenses build that classifies packages). Both reach the gate
# only via that action, so all three paths must be watched or a
# version bump silently skips the gate it changes — #814 bumped
# go-licenses touching only .settings.yaml, and #1204 bumped Go
# touching only .go-version.
- 'go.mod'
- 'go.sum'
- 'vendor/**'
- 'Makefile'
- '.go-version'
- '.settings.yaml'
- '.github/actions/install-go-licenses/**'
- '.github/actions/load-versions/**'
- '.github/workflows/merge-gate.yaml'
renovate:
# Watch every input the verify-renovate job consumes — including
# the Makefile target that runs the validator — so changes to the
# Make target or its pinned image trigger the gate.
- '.github/renovate.json5'
- 'Makefile'
bom:
# Inputs to the committed-BOM version-freshness check. The full
# `tests` job already runs it for code changes, but a docs-only
# PR editing the committed BOM (or a registry change that somehow
# lands as a docs-only diff) yields code=false and skips `tests`,
# so gate the freshness test on the doc + registry directly. The
# generator sources are included so a generator change also
# re-runs the check independently of the full suite.
- 'docs/user/container-images.md'
- 'recipes/registry.yaml'
- 'tools/bom/**'
- 'pkg/bom/**'
tuning:
# Inputs that can change the committed tuning-status table
# WITHOUT tripping pkg/tuning's TestCompute_Structure — which runs
# in the `tests` job on any non-docs change and already asserts
# every row's service, accelerator, profile, setup-name and
# tuning-name. That test is version-agnostic, so the only table
# changes it misses are a manifest pin version bump, a
# renderer/extractor change, or a hand edit of the committed doc.
# Structural changes driven by overlays, mixins, registry or
# pkg/recipe already fail that test, so they are deliberately NOT
# listed here (keeping this gate off unrelated recipe PRs).
- 'docs/integrator/components/nodewright.md'
- 'recipes/components/nodewright-customizations/**'
- 'pkg/tuning/**'
- 'tools/tuning/**'
notices:
# Inputs to the committed THIRD_PARTY_NOTICES.md freshness check.
# The file is the union of every vendored dependency's license
# across the released OS/arch matrix, so any dependency-graph
# change can alter it. Watch the dependency manifests, the vendor
# tree, and the generator itself (plus the committed file, so a
# hand edit is re-verified). .goreleaser.yaml is included because
# the generator asserts its PLATFORMS matrix matches the release
# targets defined there — a new goos/goarch must re-run this check.
# Makefile is included because the `notices`/`notices-check`
# recipes are the generation/check contract — a change to them
# must not be able to skip its own gate. .settings.yaml is included
# because the generator's output depends on its pins: a
# linting.go_licenses bump can change license classification/URL
# derivation, and a languages.go bump changes the `go list std`
# ignore set. This is a standalone job — the full `tests` suite
# does NOT regenerate the file.
# The Python half of the notices file covers the aiperf-bench
# image, whose dependency closure is an installed venv rather
# than a vendored tree. It cannot be regenerated here (that needs
# docker + PyPI), so the committed fragment is watched directly,
# alongside the requirements.txt whose sha256 the generator
# asserts the fragment still records — editing requirements
# without a refresh must fail this gate rather than ship stale
# attributions.
- 'Makefile'
- '.settings.yaml'
- '.goreleaser.yaml'
- 'go.mod'
- 'go.sum'
- 'vendor/**'
- 'tools/generate-notices'
- 'tools/generate-python-licenses'
- 'validators/performance/requirements.txt'
- 'validators/performance/licenses/**'
- 'THIRD_PARTY_NOTICES.md'
coverage:
# Inputs to the committed CUJ/CLI coverage-matrix freshness check.
# The matrix is derived, so a change to ANY of its signal sources
# can move a row without touching the doc — and until #1977 the
# only thing keeping the committed page accurate was that nobody
# regenerated it. Watched here:
# - the committed doc itself (catches a hand edit)
# - the generator and pkg/cli (the verb registry it walks)
# - infra/uat/reservations.yaml + pkg/uatbroker: which lanes are
# nightly-enrolled, and for which intents
# - the per-cloud uat-*.yaml pipelines: which CUJ phases are
# actually enabled steps (a commented-out phase is not coverage)
# - uat-nightly-batch.yaml: the previous_n version axis
# - tests/uat, tests/chainsaw, demos: the scanned signal trees
# - Makefile + this workflow: the generate/check contract and the
# coverage-freshness job itself, so a change to either cannot
# skip its own gate
- 'docs/user/coverage-matrix.md'
- 'tools/coverage/**'
- 'pkg/cli/**'
- 'pkg/uatbroker/**'
- 'infra/uat/reservations.yaml'
- '.github/workflows/uat-*.yaml'
- 'tests/uat/**'
- 'tests/chainsaw/**'
- 'demos/**'
- 'Makefile'
- '.github/workflows/merge-gate.yaml'
docs:
# Inputs to the parser-level MDX gate. Only the Fern-published
# trees matter — a change anywhere under them can introduce a
# construct that parses as CommonMark but aborts
# `fern generate --docs`.
#
# docs/index.yml is Fern's navigation manifest AND the source the
# checkers derive their file list from, so it is both an input and
# a trigger: adding a page there must run the gate over it.
# docs/README.md is the published landing page — the earlier
# per-subdirectory globs missed it entirely.
#
# Everything the gate is MADE of is listed too, so a change to the
# gate cannot skip its own verification: the two checkers, the page
# enumerator, the locked parser toolchain, this workflow, and the
# Makefile — whose `check-docs-mdx-parse` target is what the job
# actually invokes, matching why the `renovate` and `notices`
# filters watch it.
- 'docs/**'
- 'tools/check-docs-mdx'
- 'tools/check-docs-mdx-parse'
- 'tools/docs-published-files'
- 'tools/mdx/**'
- 'Makefile'
- '.github/workflows/merge-gate.yaml'
# code = true iff at least one changed file is NOT docs/markdown/LICENSE.
#
# Exclusion idiom: dorny's `predicate-quantifier: 'every'` requires a file
# to match EVERY pattern in the filter, so a file matches `code` only when
# it matches '**' AND none of the docs patterns. The filter output is true
# when ANY changed file is code — so a docs-only PR yields code=false.
#
# NOTE: 'every' does NOT mean "every changed file matches"; the prior
# `only: [**.md, docs/**, LICENSE]` form required a single file to match
# all three at once (impossible), so it was always false and code was
# always true — docs-only PRs ran full qualification. '**' matches
# dotfiles too (dorny sets picomatch dot:true), so '.golangci.yaml',
# '.settings.yaml', etc. still count as code.
- uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
id: code
with:
predicate-quantifier: 'every'
filters: |
code:
- '**'
- '!**.md'
- '!docs/**'
- '!LICENSE'
# ---------------------------------------------------------------------------
# Qualification (Test, Lint, CLI E2E, E2E, Security Scan)
# ---------------------------------------------------------------------------
tests:
needs: [check-paths]
if: needs.check-paths.outputs.code == 'true'
uses: ./.github/workflows/qualification.yaml
permissions:
actions: read
contents: read
id-token: write
security-events: write
with:
coverage_report: true
tests-skip:
needs: [check-paths]
if: needs.check-paths.outputs.code != 'true'
runs-on: ubuntu-latest
timeout-minutes: 1
steps:
- run: echo "Docs/non-code change — qualification tests not required"
# ---------------------------------------------------------------------------
# CodeQL (analyze)
# ---------------------------------------------------------------------------
analyze:
needs: [check-paths]
if: needs.check-paths.outputs.code == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
actions: read
contents: read
security-events: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/load-versions
id: versions
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: ${{ steps.versions.outputs.go }}
cache: false
- uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
with:
languages: go
- run: go build ./...
env:
GOFLAGS: -mod=vendor
- uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
analyze-skip:
needs: [check-paths]
if: needs.check-paths.outputs.code != 'true'
runs-on: ubuntu-latest
timeout-minutes: 1
steps:
- run: echo "Docs/non-code change — CodeQL analysis not required"
# ---------------------------------------------------------------------------
# Malware scan (ClamAV)
# ---------------------------------------------------------------------------
malware-scan:
needs: [check-paths]
if: needs.check-paths.outputs.code == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
actions: read
contents: read
security-events: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/malware-scan
with:
scan_path: '.'
category: 'clamav'
malware-scan-skip:
needs: [check-paths]
if: needs.check-paths.outputs.code != 'true'
runs-on: ubuntu-latest
timeout-minutes: 1
steps:
- run: echo "Docs/non-code change — malware scan not required"
# ---------------------------------------------------------------------------
# Actionlint (GitHub Actions workflow linting)
# ---------------------------------------------------------------------------
actionlint:
needs: [check-paths]
if: needs.check-paths.outputs.actions == 'true'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install actionlint
# Pin actionlint to a specific version and verify the downloaded
# binary's SHA256 against the upstream-published checksum file.
# Replaces `bash <(curl … main …)`, which executed a mutable script
# from the default branch and downloaded the binary with no
# checksum verification (the upstream installer pipes
# `curl … | tar xvz` directly).
env:
ACTIONLINT_VERSION: '1.7.11'
run: |
set -euo pipefail
BASE="https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}"
TAR="actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"
TMP="$(mktemp -d)"
curl -fsSL -o "${TMP}/${TAR}" "${BASE}/${TAR}"
curl -fsSL -o "${TMP}/checksums.txt" "${BASE}/actionlint_${ACTIONLINT_VERSION}_checksums.txt"
EXPECTED=$(awk -v t="${TAR}" '$2 == t {print $1}' "${TMP}/checksums.txt")
if [[ -z "${EXPECTED}" ]]; then
echo "::error::no checksum entry for ${TAR} in upstream checksums.txt"
exit 1
fi
ACTUAL=$(sha256sum "${TMP}/${TAR}" | awk '{print $1}')
if [[ "${ACTUAL}" != "${EXPECTED}" ]]; then
echo "::error::actionlint checksum mismatch: expected ${EXPECTED}, got ${ACTUAL}"
exit 1
fi
tar -xzf "${TMP}/${TAR}" -C "${TMP}" actionlint
mv "${TMP}/actionlint" "${PWD}/actionlint"
rm -rf "${TMP}"
echo "${PWD}" >> "$GITHUB_PATH"
- name: Run actionlint
run: actionlint -color -shellcheck=
actionlint-skip:
needs: [check-paths]
if: needs.check-paths.outputs.actions != 'true'
runs-on: ubuntu-latest
timeout-minutes: 1
steps:
- run: echo "No workflow/action changes — actionlint not required"
# ---------------------------------------------------------------------------
# License verification
# ---------------------------------------------------------------------------
verify-licenses:
needs: [check-paths]
if: needs.check-paths.outputs.deps == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/load-versions
id: versions
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: ${{ steps.versions.outputs.go }}
cache: true
cache-dependency-path: |
go.sum
vendor/modules.txt
- uses: ./.github/actions/install-go-licenses
with:
version: ${{ steps.versions.outputs.go_licenses }}
- name: Report licenses
env:
GOFLAGS: -mod=vendor
run: |
echo "=== Dependency Licenses ==="
go-licenses report ./... 2>/dev/null | sort -t',' -k3 | column -t -s','
echo ""
echo "=== License Summary ==="
go-licenses report ./... 2>/dev/null | cut -d',' -f3 | sort | uniq -c | sort -rn
- name: Check licenses
env:
GOFLAGS: -mod=vendor
run: make license-check
verify-licenses-skip:
needs: [check-paths]
if: needs.check-paths.outputs.deps != 'true'
runs-on: ubuntu-latest
timeout-minutes: 1
steps:
- run: echo "No dependency changes — license check not required"
# ---------------------------------------------------------------------------
# Renovate config validation
# ---------------------------------------------------------------------------
verify-renovate:
needs: [check-paths]
if: needs.check-paths.outputs.renovate == 'true'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Validate .github/renovate.json5
run: make lint-renovate
verify-renovate-skip:
needs: [check-paths]
if: needs.check-paths.outputs.renovate != 'true'
runs-on: ubuntu-latest
timeout-minutes: 1
steps:
- run: echo "No Renovate config changes — validation not required"
# ---------------------------------------------------------------------------
# MDX docs gate — parses every Fern-published doc with the real MDX parser.
#
# `fern check` (Fern Docs CI) does not parse MDX, and the job that does
# (`fern generate --docs --preview`) runs in a workflow_run companion whose
# status never lands on the PR head SHA, so it can never be required. That
# gap let a bare `<=` merge green and then fail every docs publish, including
# a release tag (#2050). This job is the blocking replacement: hermetic, no
# DOCS_FERN_TOKEN, no dependency on Fern's SaaS being up at merge time.
# ---------------------------------------------------------------------------
docs-mdx:
needs: [check-paths]
if: needs.check-paths.outputs.docs == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '20'
# The bash approximation runs first: it is instant and its messages name
# the specific hazard, so the common cases fail fast with better output.
# The parser then decides — it is the authoritative check, and GITHUB_ACTIONS
# makes a missing Node or a failed dependency install a hard error rather
# than the local warn-and-skip.
- name: Check MDX safety (pattern approximation)
run: make check-docs-mdx
- name: Validate docs with the real MDX parser
run: make check-docs-mdx-parse
docs-mdx-skip:
needs: [check-paths]
if: needs.check-paths.outputs.docs != 'true'
runs-on: ubuntu-latest
timeout-minutes: 1
steps:
- run: echo "No published-docs changes — MDX parse gate not required"
# ---------------------------------------------------------------------------
# BOM version freshness — ensures the committed container-images.md matches
# the registry pins even on docs-only PRs (which skip the full `tests` job).
# ---------------------------------------------------------------------------
bom-freshness:
needs: [check-paths]
if: needs.check-paths.outputs.bom == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/load-versions
id: versions
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: ${{ steps.versions.outputs.go }}
cache: false
- name: Verify committed BOM versions and variants match the recipes
env:
GOFLAGS: -mod=vendor
# `go test -run <name>` exits 0 with "no tests to run" if a test is
# renamed or removed, which would let this gate silently no-op. Assert
# each named test actually executed by requiring its PASS line — a
# docs-only PR must not be able to forge either the Components table
# or the Version variants table.
run: |
set -uo pipefail
out=$(go test ./tools/bom/... -run '^(TestCommittedBOMVersionsMatchRegistry|TestCommittedBOMVariantsMatchRecipePins)$' -v -count=1 2>&1) && rc=0 || rc=$?
echo "$out"
if [ "$rc" -ne 0 ]; then
exit "$rc"
fi
for t in TestCommittedBOMVersionsMatchRegistry TestCommittedBOMVariantsMatchRecipePins; do
if ! grep -q "^--- PASS: $t" <<<"$out"; then
echo "::error::$t did not execute (renamed or removed?); the BOM freshness gate would silently pass. Update this job to run the freshness check."
exit 1
fi
done
bom-freshness-skip:
needs: [check-paths]
if: needs.check-paths.outputs.bom != 'true'
runs-on: ubuntu-latest
timeout-minutes: 1
steps:
- run: echo "No BOM/registry changes — BOM freshness check not required"
# ---------------------------------------------------------------------------
# Tuning-status doc freshness — ensures the committed tuning table in
# docs/integrator/components/nodewright.md matches the recipe catalog and the
# nodewright package pins, on PRs touching its inputs (mirrors bom-freshness).
# ---------------------------------------------------------------------------
tuning-freshness:
needs: [check-paths]
if: needs.check-paths.outputs.tuning == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/load-versions
id: versions
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: ${{ steps.versions.outputs.go }}
cache: false
- name: Verify committed tuning-status table is up to date
env:
GOFLAGS: -mod=vendor
# `make tuning-check` regenerates the table into a temp dir and splices
# it into the doc, then fails if the working tree differs — so a stale
# committed table (or missing splice markers) fails closed here.
run: make tuning-check
tuning-freshness-skip:
needs: [check-paths]
if: needs.check-paths.outputs.tuning != 'true'
runs-on: ubuntu-latest
timeout-minutes: 1
steps:
- run: echo "No tuning-status inputs changed — freshness check not required"
# ---------------------------------------------------------------------------
# Third-party notices freshness — ensures the committed THIRD_PARTY_NOTICES.md
# matches the vendored dependency graph across every released OS/arch target,
# on PRs touching its inputs (go.mod/go.sum/vendor + the generator).
# ---------------------------------------------------------------------------
notices-freshness:
needs: [check-paths]
if: needs.check-paths.outputs.notices == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/load-versions
id: versions
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: ${{ steps.versions.outputs.go }}
cache: true
cache-dependency-path: |
go.sum
vendor/modules.txt
- uses: ./.github/actions/install-go-licenses
with:
version: ${{ steps.versions.outputs.go_licenses }}
- name: Verify committed THIRD_PARTY_NOTICES.md is up to date
env:
GOFLAGS: -mod=vendor
# `make notices-check` regenerates the notices file (union across the
# release OS/arch matrix, so it is host-independent) and fails if the
# working tree differs — a stale committed file fails closed here.
run: make notices-check
notices-freshness-skip:
needs: [check-paths]
if: needs.check-paths.outputs.notices != 'true'
runs-on: ubuntu-latest
timeout-minutes: 1
steps:
- run: echo "No dependency/generator changes — notices freshness check not required"
# ---------------------------------------------------------------------------
# Coverage-matrix freshness — ensures the committed docs/user/coverage-matrix.md
# matches the CLI registry, the nightly UAT wiring, and the in-repo test
# signals, on PRs touching its inputs (mirrors bom-freshness). Before #1977 the
# page was only accurate because contributors avoided regenerating it; this is
# the check that makes regeneration mandatory rather than optional.
# ---------------------------------------------------------------------------
coverage-freshness:
needs: [check-paths]
if: needs.check-paths.outputs.coverage == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/load-versions
id: versions
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: ${{ steps.versions.outputs.go }}
cache: false
- name: Verify committed coverage matrix is up to date
env:
GOFLAGS: -mod=vendor
# `make coverage-check` regenerates the matrix in place and fails if the
# working tree differs. The generator itself fails closed on an
# unresolvable UAT wiring or version axis, so a moved registry or renamed
# workflow input errors here instead of quietly reporting the affected
# rows as uncovered.
run: make coverage-check
coverage-freshness-skip:
needs: [check-paths]
if: needs.check-paths.outputs.coverage != 'true'
runs-on: ubuntu-latest
timeout-minutes: 1
steps:
- run: echo "No coverage-matrix inputs changed — freshness check not required"
# ---------------------------------------------------------------------------
# Aggregate gate — the single required status check
# ---------------------------------------------------------------------------
gate:
if: always()
needs:
- check-paths
- tests
- tests-skip
- analyze
- analyze-skip
- malware-scan
- malware-scan-skip
- actionlint
- actionlint-skip
- verify-licenses
- verify-licenses-skip
- verify-renovate
- verify-renovate-skip
- docs-mdx
- docs-mdx-skip
- bom-freshness
- bom-freshness-skip
- tuning-freshness
- tuning-freshness-skip
- notices-freshness
- notices-freshness-skip
- coverage-freshness
- coverage-freshness-skip
runs-on: ubuntu-latest
timeout-minutes: 1
steps:
- name: Check gate
run: |
echo "Gate results:"
echo '${{ toJSON(needs) }}' | jq -r 'to_entries[] | "\(.key): \(.value.result)"'
# check-paths must succeed — if it fails, all downstream jobs are
# skipped (not failed), which would silently pass the gate.
if [ "${{ needs.check-paths.result }}" != "success" ]; then
echo "::error::check-paths did not succeed (result: ${{ needs.check-paths.result }})"
exit 1
fi
# Fail if ANY other dependency failed or was cancelled.
# Skipped is OK — it means the inverse job ran instead.
if echo '${{ toJSON(needs.*.result) }}' | jq -e 'map(select(. == "failure" or . == "cancelled")) | length > 0' > /dev/null; then
echo "::error::One or more gating checks failed or were cancelled"
exit 1
fi