-
Notifications
You must be signed in to change notification settings - Fork 92
733 lines (686 loc) · 31 KB
/
Copy pathmerge-gate.yaml
File metadata and controls
733 lines (686 loc) · 31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
# 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: true
- uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
with:
languages: go
- run: go build ./...
env:
GOFLAGS: -mod=vendor
- uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
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: true
- 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: true
- 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: true
- 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