ci(release): allow release publishers to be dispatched individually (#4987)

* ci(release): allow release publishers to be dispatched individually

Move the TypeScript SDK and wheel publishers into reusable workflows and add
workflow_dispatch to them and to snap-publish.yml, following sync-docs.yml.
Release still calls each one. A maintainer can now publish one missing
component for an existing release without rebuilding or rerunning Release.

The snap publisher can upload artifacts from an earlier run, and a manual
latest/stable upload requires a release tag so it still rechecks the newest
stable release.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>

* ci(release): allow the Go SDK tag to be dispatched individually

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>

* ci(release): verify dispatched snap and wheel inputs match the release

A dispatched snap upload checks that its artifacts were built for the
release tag. A wheel publish first confirms the tag points to the given
commit and that each wheel is attached to that release at the given
version. artifact-run-id is declared on workflow_call so callers do not
rely on an undeclared input.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>

* ci(release): verify the provenance of dispatched snap artifacts

A manual snap upload now requires artifact-run-id to be this repository's
publishing workflow for the channel, and stable snaps must match the GitHub
Release asset digests. A skipped stable upload now reports a notice.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>

* Revert "ci(release): verify the provenance of dispatched snap artifacts"

This reverts commit 8bcf1e1e73.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>

---------

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
This commit is contained in:
Matthew Grossman
2026-10-28 00:07:24 +00:00
committed by GitHub
parent fa2d381eaa
commit 3a512d23ce
7 changed files with 355 additions and 123 deletions
+4 -1
View File
@@ -265,12 +265,15 @@ jobs:
publish-snap:
name: Publish snap
needs: build-snap
permissions:
actions: read
contents: read
uses: ./.github/workflows/snap-publish.yml
with:
upload-channel: latest/edge
github-environment: latest/edge
secrets:
publish-credentials: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
# ---------------------------------------------------------------------------
# Create / update the dev GitHub Release with CLI, gateway, driver, and wheels
+27 -118
View File
@@ -509,161 +509,70 @@ jobs:
name: Publish Snap
needs: release
if: ${{ !inputs.is-prerelease && inputs.make-latest }}
permissions:
actions: read
contents: read
uses: ./.github/workflows/snap-publish.yml
with:
upload-channel: latest/stable
github-environment: latest/stable
release-tag: ${{ inputs.release-tag }}
secrets:
publish-credentials: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
publish-prerelease-snap:
name: Publish Prerelease Snap
needs: release
if: inputs.is-prerelease
permissions:
actions: read
contents: read
uses: ./.github/workflows/snap-publish.yml
with:
upload-channel: latest/beta
release-tag: ${{ inputs.release-tag }}
github-environment: latest/beta
secrets:
publish-credentials: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
publish-sdk-typescript:
name: Publish TypeScript SDK
needs: release
if: ${{ !inputs.is-prerelease }}
runs-on: linux-amd64-cpu8
timeout-minutes: 15
permissions:
contents: read
packages: write
defaults:
run:
shell: nix shell --inputs-from . nixpkgs#nodejs_24 -c bash -euo pipefail {0}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
ref: ${{ inputs.source-sha }}
uses: ./.github/workflows/sdk-typescript-publish.yml
with:
release-tag: ${{ inputs.release-tag }}
source-ref: ${{ inputs.source-sha }}
npm-version: ${{ inputs.npm-version }}
make-latest: ${{ inputs.make-latest }}
- uses: ./.github/actions/setup-nix
- name: Install TypeScript dependencies
working-directory: ./sdk/typescript
run: npm ci
- name: Configure npm auth for GitHub Packages
working-directory: ./sdk/typescript
run: |
{
echo "@nvidia:registry=https://npm.pkg.github.com"
# Keep the token reference literal for npm to expand at publish time.
# shellcheck disable=SC2016
echo '//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}'
} > .npmrc
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
if: inputs.make-latest
with:
persist-credentials: false
ref: ${{ github.workflow_sha }}
path: release-workflow
sparse-checkout: .github/actions/check-latest-release
- name: Check current latest release
id: latest
if: inputs.make-latest
uses: ./release-workflow/.github/actions/check-latest-release
with:
release-tag: ${{ inputs.release-tag }}
- name: Publish
working-directory: ./sdk/typescript
env:
OPENSHELL_NPM_VERSION: ${{ inputs.npm-version }}
MAKE_LATEST: ${{ steps.latest.outputs.make-latest }}
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
npm pkg set version="${OPENSHELL_NPM_VERSION}"
dist_tag=latest
if [[ "${MAKE_LATEST}" != "true" ]]; then
dist_tag="release-${OPENSHELL_NPM_VERSION%.*}"
fi
npm publish --tag "${dist_tag}"
# Go resolves submodule versions from directory-prefixed tags, so the SDK at
# sdk/go needs its own sdk/go/vX.Y.Z tag to show up as a release on pkg.go.dev.
tag-sdk-go:
name: Tag Go SDK
needs: release
if: ${{ !inputs.is-prerelease }}
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: write
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ inputs.semver }}
SOURCE_SHA: ${{ inputs.source-sha }}
steps:
- name: Create Go SDK tag
run: |
set -euo pipefail
tag="sdk/go/v${VERSION}"
# Lightweight tag, so the ref points at the commit directly.
if ! existing=$(gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${tag}" --jq .object.sha 2>/dev/null); then
gh api "repos/${GITHUB_REPOSITORY}/git/refs" -f ref="refs/tags/${tag}" -f sha="${SOURCE_SHA}"
elif [[ "${existing}" != "${SOURCE_SHA}" ]]; then
echo "${tag} already exists at ${existing}, expected ${SOURCE_SHA}" >&2
exit 1
fi
# pkg.go.dev picks up versions the Go module proxy has fetched.
- name: Request version from Go module proxy
continue-on-error: true
run: curl --fail --silent --show-error --max-time 60 "https://proxy.golang.org/github.com/!n!v!i!d!i!a/!open!shell/sdk/go/@v/v${VERSION}.info"
uses: ./.github/workflows/sdk-go-tag.yml
with:
version: ${{ inputs.semver }}
source-sha: ${{ inputs.source-sha }}
trigger-wheel-publish:
name: Trigger Wheel Publish
needs: release
if: ${{ !inputs.is-prerelease }}
runs-on: [self-hosted, nv]
timeout-minutes: 10
steps:
- name: Trigger GitLab CI
env:
GITLAB_CI_TRIGGER_TOKEN: ${{ secrets.GITLAB_CI_TRIGGER_TOKEN }}
GITLAB_CI_TRIGGER_URL: ${{ secrets.GITLAB_CI_TRIGGER_URL }}
COMMIT_SHA: ${{ inputs.source-sha }}
RELEASE_VERSION: ${{ inputs.python-version }}
RELEASE_TAG: ${{ env.RELEASE_TAG }}
WHEEL_FILENAMES: ${{ needs.release.outputs.wheel_filenames }}
run: |
set -euo pipefail
if [ -z "${WHEEL_FILENAMES}" ]; then
echo "No wheel filenames provided by build job" >&2
exit 1
fi
response=$(curl -X POST \
--fail \
--silent \
--show-error \
-F "token=${GITLAB_CI_TRIGGER_TOKEN}" \
-F "ref=main" \
-F "variables[PIPELINE_ACTION]=publish_wheels" \
-F "variables[GITHUB_REPOSITORY]=${GITHUB_REPOSITORY}" \
-F "variables[COMMIT_SHA]=${COMMIT_SHA}" \
-F "variables[RELEASE_TAG]=${RELEASE_TAG}" \
-F "variables[RELEASE_VERSION]=${RELEASE_VERSION}" \
-F "variables[RELEASE_KIND]=stable" \
-F "variables[WHEEL_FILENAMES]=${WHEEL_FILENAMES}" \
"${GITLAB_CI_TRIGGER_URL}")
pipeline_id=$(printf '%s' "$response" | sed -n 's/.*"id":\([0-9][0-9]*\).*/\1/p')
pipeline_status=$(printf '%s' "$response" | sed -n 's/.*"status":"\([^"]*\)".*/\1/p')
echo "Triggered GitLab pipeline ${pipeline_id:-unknown} with status=${pipeline_status:-unknown}"
uses: ./.github/workflows/wheel-publish.yml
with:
release-tag: ${{ inputs.release-tag }}
source-sha: ${{ inputs.source-sha }}
python-version: ${{ inputs.python-version }}
wheel-filenames: ${{ needs.release.outputs.wheel_filenames }}
secrets:
GITLAB_CI_TRIGGER_TOKEN: ${{ secrets.GITLAB_CI_TRIGGER_TOKEN }}
GITLAB_CI_TRIGGER_URL: ${{ secrets.GITLAB_CI_TRIGGER_URL }}
publish-fern-docs:
name: Sync and Publish Fern Docs
+58
View File
@@ -0,0 +1,58 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
# Go resolves submodule versions from directory-prefixed tags, so the SDK at
# sdk/go needs its own sdk/go/vX.Y.Z tag to show up as a release on pkg.go.dev.
name: Tag Go SDK
on:
workflow_call:
inputs:
version:
required: true
type: string
source-sha:
required: true
type: string
workflow_dispatch:
inputs:
version:
description: Release version without the v prefix, e.g. 0.1.6
required: true
type: string
source-sha:
description: Commit the stable tag points to
required: true
type: string
permissions:
contents: write
jobs:
tag:
name: Tag Go SDK
runs-on: ubuntu-latest
timeout-minutes: 5
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ inputs.version }}
SOURCE_SHA: ${{ inputs.source-sha }}
steps:
- name: Create Go SDK tag
shell: bash
run: |
set -euo pipefail
tag="sdk/go/v${VERSION}"
# Lightweight tag, so the ref points at the commit directly.
if ! existing=$(gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${tag}" --jq .object.sha 2>/dev/null); then
gh api "repos/${GITHUB_REPOSITORY}/git/refs" -f ref="refs/tags/${tag}" -f sha="${SOURCE_SHA}"
elif [[ "${existing}" != "${SOURCE_SHA}" ]]; then
echo "${tag} already exists at ${existing}, expected ${SOURCE_SHA}" >&2
exit 1
fi
# pkg.go.dev picks up versions the Go module proxy has fetched.
- name: Request version from Go module proxy
continue-on-error: true
shell: bash
run: curl --fail --silent --show-error --max-time 60 "https://proxy.golang.org/github.com/!n!v!i!d!i!a/!open!shell/sdk/go/@v/v${VERSION}.info"
@@ -0,0 +1,102 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
name: Publish TypeScript SDK
on:
workflow_call:
inputs:
release-tag:
required: true
type: string
source-ref:
description: Commit to build the package from
required: true
type: string
npm-version:
required: true
type: string
make-latest:
description: Whether this release may update the npm latest dist-tag
required: false
default: false
type: boolean
workflow_dispatch:
inputs:
release-tag:
description: Stable tag to publish, e.g. v0.1.6
required: true
type: string
npm-version:
description: npm package version, e.g. 0.1.6
required: true
type: string
make-latest:
description: Update the latest dist-tag if this is still the newest stable release
required: false
default: true
type: boolean
permissions:
contents: read
packages: write
jobs:
publish:
name: Publish TypeScript SDK
runs-on: linux-amd64-cpu8
timeout-minutes: 15
defaults:
run:
shell: nix shell --inputs-from . nixpkgs#nodejs_24 -c bash -euo pipefail {0}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
ref: ${{ inputs.source-ref || inputs.release-tag }}
- uses: ./.github/actions/setup-nix
- name: Install TypeScript dependencies
working-directory: ./sdk/typescript
run: npm ci
- name: Configure npm auth for GitHub Packages
working-directory: ./sdk/typescript
run: |
{
echo "@nvidia:registry=https://npm.pkg.github.com"
# Keep the token reference literal for npm to expand at publish time.
# shellcheck disable=SC2016
echo '//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}'
} > .npmrc
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
if: inputs.make-latest
with:
persist-credentials: false
ref: ${{ github.workflow_sha }}
path: release-workflow
sparse-checkout: .github/actions/check-latest-release
- name: Check current latest release
id: latest
if: inputs.make-latest
uses: ./release-workflow/.github/actions/check-latest-release
with:
release-tag: ${{ inputs.release-tag }}
- name: Publish
working-directory: ./sdk/typescript
env:
OPENSHELL_NPM_VERSION: ${{ inputs.npm-version }}
MAKE_LATEST: ${{ steps.latest.outputs.make-latest }}
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
npm pkg set version="${OPENSHELL_NPM_VERSION}"
dist_tag=latest
if [[ "${MAKE_LATEST}" != "true" ]]; then
dist_tag="release-${OPENSHELL_NPM_VERSION%.*}"
fi
npm publish --tag "${dist_tag}"
+40 -3
View File
@@ -17,11 +17,36 @@ on:
required: false
type: string
default: ""
artifact-run-id:
description: Run whose snap-linux-* artifacts are uploaded; defaults to the current run
required: false
type: string
default: ""
secrets:
publish-credentials:
SNAPCRAFT_STORE_CREDENTIALS:
required: true
workflow_dispatch:
inputs:
upload-channel:
description: Snap Store channel, also used as the GitHub environment
required: true
type: choice
options:
- latest/stable
- latest/beta
- latest/edge
release-tag:
description: Stable tag to recheck before updating the stable channel; required for latest/stable
required: false
type: string
default: ""
artifact-run-id:
description: Run whose snap-linux-* artifacts are uploaded
required: true
type: string
permissions:
actions: read
contents: read
defaults:
@@ -40,7 +65,7 @@ jobs:
runner: linux-arm64-cpu8
runs-on: ${{ matrix.runner }}
timeout-minutes: 10
environment: ${{ inputs.github-environment }}
environment: ${{ inputs.github-environment || inputs.upload-channel }}
concurrency:
group: snap-publish-${{ inputs.upload-channel }}-${{ matrix.arch }}
cancel-in-progress: false
@@ -58,6 +83,8 @@ jobs:
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: snap-linux-${{ matrix.arch }}
run-id: ${{ inputs.artifact-run-id || github.run_id }}
github-token: ${{ inputs.artifact-run-id && github.token || '' }}
- name: Install snapcraft
run: |
@@ -79,10 +106,15 @@ jobs:
- name: Upload snap to Snap Store
if: ${{ inputs.release-tag == '' || steps.latest.outputs.make-latest == 'true' }}
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.publish-credentials }}
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
INPUTS_UPLOAD_CHANNEL: ${{ inputs.upload-channel }}
INPUTS_RELEASE_TAG: ${{ inputs.release-tag }}
run: |
set -euo pipefail
if [[ "${INPUTS_UPLOAD_CHANNEL}" == latest/stable && -z "${INPUTS_RELEASE_TAG}" ]]; then
echo "latest/stable uploads require release-tag" >&2
exit 1
fi
shopt -s nullglob
snaps=(*.snap)
if [[ "${#snaps[@]}" -ne 1 ]]; then
@@ -92,6 +124,11 @@ jobs:
snap_file="${snaps[0]}"
snap_name="${snap_file%.snap}"
snap_name="${snap_name%%_*}"
# Artifacts can come from another run, so confirm they were built for this tag.
if [[ -n "${INPUTS_RELEASE_TAG}" && "${snap_file}" != "${snap_name}_${INPUTS_RELEASE_TAG#v}_"* ]]; then
echo "${snap_file} was not built for ${INPUTS_RELEASE_TAG}" >&2
exit 1
fi
component_args=()
for comp in "${snap_name}"+*.comp; do
component_file="${comp##*/}"
+120
View File
@@ -0,0 +1,120 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
name: Publish Wheels
on:
workflow_call:
inputs:
release-tag:
required: true
type: string
source-sha:
required: true
type: string
python-version:
required: true
type: string
wheel-filenames:
required: true
type: string
secrets:
GITLAB_CI_TRIGGER_TOKEN:
required: false
GITLAB_CI_TRIGGER_URL:
required: false
workflow_dispatch:
inputs:
release-tag:
description: Stable tag whose GitHub Release holds the wheels, e.g. v0.1.6
required: true
type: string
source-sha:
description: Commit the tag points to
required: true
type: string
python-version:
description: Python package version, e.g. 0.1.6
required: true
type: string
wheel-filenames:
description: Wheel filenames attached to the GitHub Release
required: true
type: string
permissions:
contents: read
jobs:
# Dispatch inputs are free-form, so confirm they describe a published release.
verify:
name: Verify Release Inputs
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check tag, commit and wheels
shell: bash
env:
GH_TOKEN: ${{ github.token }}
COMMIT_SHA: ${{ inputs.source-sha }}
RELEASE_VERSION: ${{ inputs.python-version }}
RELEASE_TAG: ${{ inputs.release-tag }}
WHEEL_FILENAMES: ${{ inputs.wheel-filenames }}
run: |
set -euo pipefail
tag_sha=$(gh api "repos/${GITHUB_REPOSITORY}/commits/${RELEASE_TAG}" --jq .sha)
if [[ "${tag_sha}" != "${COMMIT_SHA}" ]]; then
echo "${RELEASE_TAG} points to ${tag_sha}, not ${COMMIT_SHA}" >&2
exit 1
fi
assets=$(gh release view "${RELEASE_TAG}" -R "${GITHUB_REPOSITORY}" --json assets --jq '.assets[].name')
for wheel in ${WHEEL_FILENAMES//,/ }; do
if ! grep -qxF "${wheel}" <<<"${assets}"; then
echo "${wheel} is not attached to the ${RELEASE_TAG} release" >&2
exit 1
fi
if [[ "${wheel}" != *"-${RELEASE_VERSION}-"* ]]; then
echo "${wheel} is not version ${RELEASE_VERSION}" >&2
exit 1
fi
done
trigger:
name: Trigger Wheel Publish
needs: verify
runs-on: [self-hosted, nv]
timeout-minutes: 10
steps:
- name: Trigger GitLab CI
shell: bash
env:
GITLAB_CI_TRIGGER_TOKEN: ${{ secrets.GITLAB_CI_TRIGGER_TOKEN }}
GITLAB_CI_TRIGGER_URL: ${{ secrets.GITLAB_CI_TRIGGER_URL }}
COMMIT_SHA: ${{ inputs.source-sha }}
RELEASE_VERSION: ${{ inputs.python-version }}
RELEASE_TAG: ${{ inputs.release-tag }}
WHEEL_FILENAMES: ${{ inputs.wheel-filenames }}
run: |
set -euo pipefail
if [ -z "${WHEEL_FILENAMES}" ]; then
echo "No wheel filenames provided by build job" >&2
exit 1
fi
response=$(curl -X POST \
--fail \
--silent \
--show-error \
-F "token=${GITLAB_CI_TRIGGER_TOKEN}" \
-F "ref=main" \
-F "variables[PIPELINE_ACTION]=publish_wheels" \
-F "variables[GITHUB_REPOSITORY]=${GITHUB_REPOSITORY}" \
-F "variables[COMMIT_SHA]=${COMMIT_SHA}" \
-F "variables[RELEASE_TAG]=${RELEASE_TAG}" \
-F "variables[RELEASE_VERSION]=${RELEASE_VERSION}" \
-F "variables[RELEASE_KIND]=stable" \
-F "variables[WHEEL_FILENAMES]=${WHEEL_FILENAMES}" \
"${GITLAB_CI_TRIGGER_URL}")
pipeline_id=$(printf '%s' "$response" | sed -n 's/.*"id":\([0-9][0-9]*\).*/\1/p')
pipeline_status=$(printf '%s' "$response" | sed -n 's/.*"status":"\([^"]*\)".*/\1/p')
echo "Triggered GitLab pipeline ${pipeline_id:-unknown} with status=${pipeline_status:-unknown}"
+4 -1
View File
@@ -718,7 +718,10 @@ These workflows run after merge to publish dev/tagged artifacts and verify them.
| `.github/workflows/release-notes.yml` | Shared prompt-driven release notes generation through Inference Hub, used by prerelease previews, notes-only previews, and stable publication when no preview is reused. |
| `.github/workflows/release-qualification.yml` | Reusable qualification suites and result aggregation. Runs security scans, conformance and feature integration, Docker and VM E2E, protobuf compatibility, and advisory compatibility review. |
| `.github/workflows/snap-package.yml` | Builds snap and component artifacts for Release Dev, Release, and Pre-release without Store credentials or publication. |
| `.github/workflows/snap-publish.yml` | Uploads existing snap and component artifacts directly to a Store channel without rebuilding. Release Dev uses `latest/edge`, Pre-release uses `latest/beta`, and Release uses `latest/stable` for the stable rebuild. Uploads queue per channel and architecture, so tagged beta and stable jobs recheck repository tags after earlier publication finishes and before updating their floating channels. |
| `.github/workflows/snap-publish.yml` | Uploads existing snap and component artifacts directly to a Store channel without rebuilding. Release Dev uses `latest/edge`, Pre-release uses `latest/beta`, and Release uses `latest/stable` for the stable rebuild. Uploads queue per channel and architecture, so tagged beta and stable jobs recheck repository tags after earlier publication finishes and before updating their floating channels. Manual dispatch uploads the `snap-linux-*` artifacts from `artifact-run-id`. |
| `.github/workflows/sdk-typescript-publish.yml` | Publishes the TypeScript SDK to GitHub Packages. Called by stable Release; manual dispatch publishes a given tag and npm version. |
| `.github/workflows/sdk-go-tag.yml` | Creates the `sdk/go/vX.Y.Z` tag and asks the Go module proxy to fetch it. Called by stable Release; manual dispatch tags a given version and commit. |
| `.github/workflows/wheel-publish.yml` | Triggers the GitLab pipeline that publishes wheels from a GitHub Release to PyPI. Called by stable Release; manual dispatch republishes a given tag. |
| `.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 `ubuntu-snap-system-docker` job verifies a compatible system Docker lifecycle, while `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. |
## Required status contexts