Skip to content

Commit 393d75c

Browse files
committed
fix(ci): gate tagged publication on qualification
Signed-off-by: Simon Scatton <sscatton@nvidia.com>
1 parent e7fdd6b commit 393d75c

9 files changed

Lines changed: 338 additions & 40 deletions

File tree

‎.agents/skills/watch-github-actions/SKILL.md‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,12 @@ During `0.x`, a minor train permits compatibility findings
147147
as warnings; a patch train or no active train rejects them. Compare the current
148148
train's version with the latest stable release; commit messages are irrelevant.
149149
Compilation, baseline, and tool errors remain fatal. The `protobuf_compatibility` suite participates in
150-
the `release-tag-v1` qualification profile. Failed qualification prevents stable
151-
publication but still allows pre-release artifacts to publish with the failure
152-
recorded.
150+
the `release-tag-v1` qualification profile. Both tagged pre-release and stable
151+
publication require this profile to pass. Failed, cancelled, or skipped suites
152+
block publication; build artifacts and qualification evidence remain in Actions
153+
storage for diagnosis. Source-SHA images are staging inputs for qualification.
154+
Snap builds run in parallel with qualification, but tagged stable Store uploads
155+
consume those built artifacts only after qualification passes.
153156

154157
View logs for a specific run:
155158

‎.github/workflows/branch-checks.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,11 @@ jobs:
411411
- name: Test
412412
run: mise run test:python
413413

414+
- name: Test release publication gates
415+
run: |
416+
mise run test:qualification-summary
417+
mise run test:release-publication
418+
414419
- name: Test local gateway configuration helpers
415420
run: |
416421
bash tasks/scripts/test-gateway-pull-policy.sh

‎.github/workflows/release-tag.yml‎

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,10 @@ jobs:
311311
retention-days: 90
312312
if-no-files-found: error
313313

314-
- name: Require current qualification profile for stable releases
315-
if: needs.compute-versions.outputs.is_prerelease != 'true' && steps.summary.outputs.current-profile-passed != 'true'
314+
- name: Require current qualification profile before publication
315+
if: steps.summary.outputs.current-profile-passed != 'true'
316316
run: |
317-
echo "Stable release ${RELEASE_TAG} did not pass the current release-tag-v1 qualification profile." >&2
317+
echo "Release ${RELEASE_TAG} did not pass the current release-tag-v1 qualification profile." >&2
318318
exit 1
319319
320320
build-python-wheel:
@@ -372,7 +372,7 @@ jobs:
372372
checkout-ref: ${{ needs.compute-versions.outputs.source_sha }}
373373
upload-channel: ${{ needs.compute-versions.outputs.is_prerelease == 'true' && 'latest/edge' || 'latest/stable' }}
374374
github-environment: ${{ needs.compute-versions.outputs.is_prerelease == 'true' && 'latest/edge' || 'latest/stable' }}
375-
publish: ${{ needs.compute-versions.outputs.is_prerelease != 'true' }}
375+
publish: false
376376
secrets:
377377
publish-credentials: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
378378

@@ -386,11 +386,23 @@ jobs:
386386
rpm-release: ${{ needs.compute-versions.outputs.rpm_release }}
387387
cargo-version: ${{ needs.compute-versions.outputs.cargo_version }}
388388

389+
publish-snap:
390+
name: Publish Snap
391+
needs: [compute-versions, release, qualification-result]
392+
if: needs.compute-versions.outputs.is_prerelease != 'true' && needs.qualification-result.outputs.current-profile-passed == 'true'
393+
uses: ./.github/workflows/snap-publish.yml
394+
with:
395+
upload-channel: latest/stable
396+
github-environment: latest/stable
397+
secrets:
398+
publish-credentials: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
399+
389400
# ---------------------------------------------------------------------------
390-
# Assemble release artifacts. Stable publication remains qualification-gated.
401+
# Assemble release artifacts after the current qualification profile passes.
391402
# ---------------------------------------------------------------------------
392403
release:
393404
name: Release
405+
if: needs.qualification-result.outputs.current-profile-passed == 'true'
394406
needs:
395407
- compute-versions
396408
- package-binaries
@@ -740,9 +752,10 @@ jobs:
740752
publish-qualification:
741753
name: Publish Qualification Summary (OCI)
742754
if: >-
743-
always()
755+
!cancelled()
744756
&& needs.release.result == 'success'
745757
&& needs.qualification-result.result == 'success'
758+
&& needs.qualification-result.outputs.current-profile-passed == 'true'
746759
needs: [compute-versions, qualification-result, release]
747760
runs-on: ubuntu-latest
748761
timeout-minutes: 5

‎.github/workflows/snap-package.yml‎

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ jobs:
4949
runner: linux-arm64-cpu8
5050
runs-on: ${{ matrix.runner }}
5151
timeout-minutes: 60
52-
environment: ${{ inputs.github-environment }}
5352
steps:
5453
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
5554
with:
@@ -173,23 +172,12 @@ jobs:
173172
*.comp
174173
retention-days: 5
175174

176-
- name: Upload snap to Snap Store
177-
if: inputs.publish
178-
env:
179-
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.publish-credentials }}
180-
INPUTS_UPLOAD_CHANNEL: ${{ inputs.upload-channel }}
181-
run: |
182-
set -euo pipefail
183-
SNAP_FILE="${{ steps.capture.outputs.snap-file }}"
184-
SNAP_NAME="${SNAP_FILE%.snap}"
185-
SNAP_NAME="${SNAP_NAME%%_*}"
186-
187-
COMPONENT_ARGS=()
188-
shopt -s nullglob
189-
for comp in "${SNAP_NAME}"+*.comp; do
190-
echo "Adding component: $comp"
191-
COMPONENT_ARGS+=(--component "$comp")
192-
done
193-
194-
echo "Uploading $SNAP_FILE to ${INPUTS_UPLOAD_CHANNEL}"
195-
snapcraft upload --release "${INPUTS_UPLOAD_CHANNEL}" "$SNAP_FILE" "${COMPONENT_ARGS[@]}"
175+
publish-snap:
176+
needs: build-snap
177+
if: inputs.publish
178+
uses: ./.github/workflows/snap-publish.yml
179+
with:
180+
upload-channel: ${{ inputs.upload-channel }}
181+
github-environment: ${{ inputs.github-environment }}
182+
secrets:
183+
publish-credentials: ${{ secrets.publish-credentials }}

‎.github/workflows/snap-publish.yml‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
name: Publish Snap
5+
6+
on:
7+
workflow_call:
8+
inputs:
9+
upload-channel:
10+
required: true
11+
type: string
12+
github-environment:
13+
required: true
14+
type: string
15+
secrets:
16+
publish-credentials:
17+
required: true
18+
19+
permissions:
20+
contents: read
21+
22+
defaults:
23+
run:
24+
shell: bash
25+
26+
jobs:
27+
publish:
28+
name: Publish Snap (Linux ${{ matrix.arch }})
29+
strategy:
30+
matrix:
31+
include:
32+
- arch: amd64
33+
runner: linux-amd64-cpu8
34+
- arch: arm64
35+
runner: linux-arm64-cpu8
36+
runs-on: ${{ matrix.runner }}
37+
timeout-minutes: 10
38+
environment: ${{ inputs.github-environment }}
39+
steps:
40+
- name: Download built snap and components
41+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
42+
with:
43+
name: snap-linux-${{ matrix.arch }}
44+
45+
- name: Install snapcraft
46+
run: |
47+
set -euo pipefail
48+
sudo apt-get update
49+
sudo apt-get install -y snapd
50+
sudo systemctl enable --now snapd.socket
51+
sudo systemctl start snapd
52+
sudo snap wait system seed.loaded
53+
sudo snap install snapcraft --classic
54+
55+
- name: Upload snap to Snap Store
56+
env:
57+
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.publish-credentials }}
58+
INPUTS_UPLOAD_CHANNEL: ${{ inputs.upload-channel }}
59+
run: |
60+
set -euo pipefail
61+
shopt -s nullglob
62+
snaps=(*.snap)
63+
if [[ "${#snaps[@]}" -ne 1 ]]; then
64+
echo "Expected exactly one built snap, found ${#snaps[@]}" >&2
65+
exit 1
66+
fi
67+
snap_file="${snaps[0]}"
68+
snap_name="${snap_file%.snap}"
69+
snap_name="${snap_name%%_*}"
70+
component_args=()
71+
for comp in "${snap_name}"+*.comp; do
72+
component_args+=(--component "$comp")
73+
done
74+
snapcraft upload --release "${INPUTS_UPLOAD_CHANNEL}" "$snap_file" "${component_args[@]}"

‎CI.md‎

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ missing baselines, merge conflicts, and tool failures always fail the check.
4242

4343
Release Tag compares the tagged candidate cumulatively against the previous
4444
stable release, using the same minor-versus-patch policy. Its result is part
45-
of the qualification profile: failure blocks stable publication, while a
46-
pre-release can still publish with failed qualification recorded. This checks
45+
of the qualification profile: failure blocks both pre-release and stable
46+
publication. Failed candidates retain build artifacts and evidence in Actions. This checks
4747
protobuf compatibility; SDK/configuration compatibility and migration review
4848
remain separate qualification work.
4949

@@ -187,9 +187,8 @@ candidate snapshot. Cargo Deny uses its existing NVIDIA self-hosted runner and
187187
CI container.
188188

189189
Tagged releases treat Cargo Deny and Codex Security findings as failures of the
190-
currently implemented qualification profile. A profile failure does not prevent
191-
a pre-release candidate's complete artifact set from being published, but it
192-
does prevent stable publication. CodeQL, Trivy, and Zizmor findings are
190+
currently implemented qualification profile. A profile failure prevents both
191+
pre-release and stable publication. CodeQL, Trivy, and Zizmor findings are
193192
temporarily informational for tagged releases: the existing findings were
194193
reviewed and accepted for v0.1.0 and will be addressed in 0.1.x releases.
195194
Scanner failures still fail qualification.
@@ -208,8 +207,9 @@ gh workflow run security-scan.yml --ref main \
208207
-F fail-on-static-findings=false
209208
```
210209

211-
To integrate it into a larger workflow, run it after the job that pushes the
212-
candidate tag and publishes the artifacts. This example assumes an existing
210+
To integrate it into a larger workflow, run it after the job that creates the
211+
candidate tag and stages source-addressed artifacts for scanning. Release-facing
212+
publication follows successful qualification. This example assumes an existing
213213
`build` job with outputs named `candidate_tag`, `gateway_image`, `sandbox_image`,
214214
and `chart_ref`; adapt those names to your workflow:
215215

@@ -240,8 +240,8 @@ jobs:
240240
Set `needs: security` on a downstream promotion job to require successful scans.
241241
The tagged release workflow records protobuf, security, and integration outcomes in a
242242
qualification job after publishing its commit-addressed OCI images. A failed
243-
check remains visible in the workflow, but pre-release artifact assembly and
244-
publication continue. Stable publication currently requires the implemented
243+
check remains visible in the workflow, with build artifacts and evidence retained
244+
in Actions storage. Both pre-release and stable publication require the implemented
245245
`release-tag-v1` profile to pass; that profile is an incremental subset of RFC
246246
0014 qualification.
247247

@@ -497,7 +497,8 @@ These workflows run after merge to publish dev/tagged artifacts and verify them.
497497
| File | Role |
498498
|---|---|
499499
| `.github/workflows/release-dev.yml` | Publishes the rolling `dev` build on every push to `main`. Builds gateway, sandbox, and supervisor images and binaries, packages, wheels, and pushes the Helm chart as `oci://ghcr.io/nvidia/openshell/helm-chart:0.0.0-dev` (plus an immutable `0.0.0-dev.<sha>` pin). Also dispatchable manually. |
500-
| `.github/workflows/release-tag.yml` | Publishes tagged stable releases and manually dispatched pre-releases. Its automatic tag trigger excludes `-pre.*`. Protobuf, security, and integration failures do not block pre-release artifact publication. Stable publication requires the currently implemented qualification profile to pass; the summary identifies the remaining RFC 0014 coverage. |
500+
| `.github/workflows/release-tag.yml` | Publishes tagged stable releases and manually dispatched pre-releases. Its automatic tag trigger excludes `-pre.*`. Both require the currently implemented qualification profile to pass before publication; the summary identifies the remaining RFC 0014 coverage. Failed candidates retain build artifacts and evidence in Actions storage. Source-SHA OCI images remain available as qualification inputs. |
501+
| `.github/workflows/snap-publish.yml` | Uploads existing Snap and component artifacts to the Store without rebuilding. Release Dev calls it after Snap builds; Release Tag calls it only after qualification and release assembly succeed. |
501502
| `.github/workflows/release-canary.yml` | Smoke-tests published dev artifacts in the `macos`, `ubuntu-deb`, `ubuntu-snap-system-docker`, `fedora`, and `kubernetes` (kind + Helm) jobs. Each job reaches its gateway and creates, exercises, and deletes a sandbox. The Snap lanes verify a compatible system Docker lifecycle and `ubuntu-snap-docker-preflight` tests fail-fast behavior when Docker is absent or supplied by the Docker snap. The positive Snap lane also runs a local policy containment check with the packaged prover. It runs automatically after `Release Dev` succeeds and supports manual dispatch (`gh workflow run release-canary.yml --ref <branch>`). See the `test-release-canary` skill for the playbook and local kind reproduction. |
502503

503504
## Required status contexts

‎docs/about/support-matrix.mdx‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ the release cycle. Use a stable release for production deployments.
1919
| Pre-release | Built nightly when `main` has changed and normal CI passes. Versions use the form `X.Y.Z-pre.N`. | Validating the immutable artifact set proposed for the next stable release. Pre-releases use the stable feature set but may not have passed qualification. |
2020
| Stable | Promoted from a pre-release that passes conformance, upgrade, API compatibility, artifact, and security checks. Versions use the form `X.Y.Z`. | Production use within this support matrix. |
2121

22+
The tagged release pipeline publishes pre-release and stable artifacts only
23+
after its current qualification profile passes. Failed candidates retain build
24+
artifacts and qualification evidence in CI storage. The current profile covers
25+
part of the complete release qualification policy; passing it does not establish
26+
full RFC 0014 coverage.
27+
2228
Tagged stable releases generally go out every week. OpenShell targets Tuesday
2329
publication when there are changes and every blocking qualification check
2430
passes. A security release may ship sooner.

0 commit comments

Comments
 (0)