Skip to content

ci: make cargo-deny advisory checks non-blocking #8789

ci: make cargo-deny advisory checks non-blocking

ci: make cargo-deny advisory checks non-blocking #8789

Workflow file for this run

name: CI
on:
merge_group:
push:
branches: [main]
pull_request:
workflow_dispatch:
# Cancel in-progress runs for PRs, queue for merge group and main
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}-v2
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
# Single source of truth for supported Python versions
SUPPORTED_PYTHONS: '["3.10", "3.11", "3.12", "3.13"]'
# Default Python version for non-matrix jobs
PYTHON_VERSION: "3.13"
# Minimum supported Python — used for ABI3 wheel builds and glob patterns.
# Must match the lowest entry in SUPPORTED_PYTHONS.
MINIMUM_PYTHON: "3.10"
# Number of runners to shard integration tests across (per runtime)
# Slow tests ([short] skip) are distributed round-robin first, then fast tests fill in
NUM_IT_RUNNER_SHARDS: "4"
# Standard environment
HYPOTHESIS_PROFILE: ci
FORCE_COLOR: "1"
PIP_DISABLE_PIP_VERSION_CHECK: "1"
PIP_NO_PYTHON_VERSION_WARNING: "1"
CARGO_TERM_COLOR: always
# CGo required for go-tree-sitter (static Python schema parser)
CGO_ENABLED: "1"
# Note: do NOT add rustup/rustup-init to MISE_DISABLE_TOOLS — mise needs
# them to install Rust components (rustfmt, clippy) specified in mise.toml.
#
# mise cache & Rust components:
# mise-action caches ~/.local/share/mise but NOT ~/.rustup. On cache hit,
# mise sees rust as "installed" (symlink exists) and skips reinstall, but
# the actual rustup toolchain + components (rustfmt, clippy) are missing.
# Rust jobs must run `rustup component add` after mise-action to ensure
# components are present regardless of cache state.
#
# mise cache keys: each job uses its own key (mise-ci-{job_id}) so parallel
# jobs don't race on a single shared cache entry.
permissions: {}
jobs:
# Passthrough for SUPPORTED_PYTHONS — env context is unavailable in
# strategy.matrix, so matrix jobs reference this job's output instead.
setup:
name: Setup
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
supported_pythons: ${{ env.SUPPORTED_PYTHONS }}
steps:
- run: echo "supported_pythons=$SUPPORTED_PYTHONS"
# =============================================================================
# Version Check - Validates VERSION.txt format and sync
# =============================================================================
version-check:
name: Validate version
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Validate version
run: |
# Read canonical version from VERSION.txt
if [ ! -f VERSION.txt ]; then
echo "::error::VERSION.txt not found"
exit 1
fi
VERSION=$(tr -d '[:space:]' < VERSION.txt)
echo "VERSION.txt: $VERSION"
# Validate semver format
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
echo "::error::Invalid version format: $VERSION"
echo "::error::Expected semver format: MAJOR.MINOR.PATCH or MAJOR.MINOR.PATCH-prerelease"
exit 1
fi
echo "✓ Valid semver format"
# Check VERSION.txt matches crates/Cargo.toml
CARGO_VERSION=$(grep '^version = ' crates/Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
if [ "$VERSION" != "$CARGO_VERSION" ]; then
echo "::error::Version mismatch! VERSION.txt has $VERSION but crates/Cargo.toml has $CARGO_VERSION"
echo "::error::Run 'mise run version:bump $VERSION' to sync."
exit 1
fi
echo "✓ VERSION.txt matches crates/Cargo.toml"
# Get the highest existing stable version tag
HIGHEST_TAG=$(git tag -l 'v[0-9]*.[0-9]*.[0-9]*' | grep -v '-' | sed 's/^v//' | sort -V | tail -1)
if [ -n "$HIGHEST_TAG" ]; then
echo "Highest released version: $HIGHEST_TAG"
# Check it's not a downgrade (using sort -V for proper semver comparison)
BASE_VERSION="${VERSION%%-*}"
SORTED_HIGHEST=$(printf '%s\n%s' "$HIGHEST_TAG" "$BASE_VERSION" | sort -V | tail -1)
if [ "$SORTED_HIGHEST" = "$HIGHEST_TAG" ] && [ "$HIGHEST_TAG" != "$BASE_VERSION" ]; then
echo "::error::Cannot downgrade version from $HIGHEST_TAG to $VERSION"
echo "::error::New version must be greater than the highest released version."
exit 1
fi
echo "✓ Version is not a downgrade"
else
echo "No existing version tags found"
fi
echo ""
echo "✓ Version $VERSION is valid for release"
# =============================================================================
# Build Stage - Produces artifacts for downstream jobs
# =============================================================================
build-sdk:
name: Build SDK
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: jdx/mise-action@v4
with:
cache_key_prefix: mise-ci-${{ github.job }}
- name: Build SDK
run: mise run ci:build:sdk
- name: Upload SDK package
uses: actions/upload-artifact@v6
with:
name: CogPackage
path: dist/cog-*
build-rust:
name: Build coglet wheel
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: Swatinem/rust-cache@v2
with:
workspaces: crates -> target
save-if: ${{ github.ref == 'refs/heads/main' }}
# maturin-action bundles maturin and zig inside a manylinux container.
# Explicitly request MINIMUM_PYTHON inside the container so maturin
# produces an ABI3 wheel (cp310-abi3). Without this, maturin picks up
# the container's default Python (3.8), which doesn't support ABI3.
- name: Build coglet wheel (ABI3)
uses: PyO3/maturin-action@v1
with:
target: x86_64-unknown-linux-gnu
args: --release --out dist -m crates/coglet-python/Cargo.toml --interpreter python${{ env.MINIMUM_PYTHON }}
manylinux: auto
- name: Verify ABI3 wheel exists
run: |
CPVER="cp${MINIMUM_PYTHON//.}"
ls -la dist/coglet-*-${CPVER}-abi3-*.whl
- name: Upload coglet wheel
uses: actions/upload-artifact@v6
with:
name: CogletRustWheel
# ABI3 wheels use cpXYZ-abi3 naming; just match any abi3 wheel
path: dist/coglet-*-abi3-*.whl
build-cog:
name: Build cog CLI
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: jdx/mise-action@v4
with:
cache_key_prefix: mise-ci-${{ github.job }}
- name: Get version from VERSION.txt
id: version
run: echo "version=$(tr -d '[:space:]' < VERSION.txt)" >> "$GITHUB_OUTPUT"
- name: Build cog binary
uses: goreleaser/goreleaser-action@v7
with:
version: '~> v2'
args: build --clean --snapshot --single-target --id cog --output cog
env:
GOFLAGS: -buildvcs=false
# Use VERSION.txt as version source so snapshot builds match the wheel version
COG_VERSION: ${{ steps.version.outputs.version }}
- name: Upload cog binary
uses: actions/upload-artifact@v6
with:
name: CogBinary
path: cog
# =============================================================================
# Format Checks - Fast, parallel
# =============================================================================
fmt-go:
name: Format Go
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
cache_key_prefix: mise-ci-${{ github.job }}
- name: Check Go formatting
run: mise run fmt:go
fmt-rust:
name: Format Rust
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
cache_key_prefix: mise-ci-${{ github.job }}
- name: Ensure Rust components
run: rustup component add rustfmt clippy
- name: Check Rust formatting
run: mise run fmt:rust
fmt-python:
name: Format Python
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
cache_key_prefix: mise-ci-${{ github.job }}
- name: Check Python formatting
run: mise run fmt:python
check-llm-docs:
name: Check LLM docs
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
cache_key_prefix: mise-ci-${{ github.job }}
- name: Check llms.txt is up to date
run: mise run docs:llm:check
- name: Check CLI docs are up to date
run: mise run docs:cli:check
check-stubs:
name: Check stubs
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
# setup-python provides a shared-library Python (libpython3.x.so) needed
# by stub_gen at runtime (PyO3 auto-initialize). mise's
# python-build-standalone is statically linked and doesn't ship the .so.
- uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- uses: Swatinem/rust-cache@v2
with:
workspaces: crates -> target
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: jdx/mise-action@v4
with:
cache_key_prefix: mise-ci-${{ github.job }}
- name: Check stubs are up to date
run: |
# stub_gen links libpython dynamically; tell the linker where to find it
export LD_LIBRARY_PATH="$(python3 -c 'import sysconfig; print(sysconfig.get_config_var("LIBDIR"))')"
mise run --force stub:check
# =============================================================================
# Lint Checks - Parallel
# =============================================================================
lint-go:
name: Lint Go
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
cache_key_prefix: mise-ci-${{ github.job }}
- name: Lint Go
run: mise run lint:go
lint-rust:
name: Lint Rust
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
- uses: Swatinem/rust-cache@v2
with:
workspaces: crates -> target
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: jdx/mise-action@v4
with:
cache_key_prefix: mise-ci-${{ github.job }}
- name: Ensure Rust components
run: rustup component add rustfmt clippy
- name: Lint Rust (clippy)
run: mise run lint:rust
lint-rust-deny:
name: Lint Rust (deny)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
cache_key_prefix: mise-ci-${{ github.job }}
- name: Check licenses, bans, and sources
run: cargo deny --manifest-path crates/Cargo.toml check bans licenses sources
# Advisory checks run against a live database that can change at any time.
# A new RUSTSEC entry would break CI on every branch even though no code
# changed, so this job is informational only (continue-on-error).
# A weekly cron workflow (rust-advisories.yaml) opens issues for new advisories.
lint-rust-advisories:
name: Lint Rust (advisories)
runs-on: ubuntu-latest
timeout-minutes: 10
continue-on-error: true
steps:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
cache_key_prefix: mise-ci-${{ github.job }}
- name: Check advisories (informational)
run: cargo deny --manifest-path crates/Cargo.toml check advisories
lint-python:
name: Lint Python
needs: [build-sdk, build-rust]
runs-on: ubuntu-latest-8-cores
timeout-minutes: 15
steps:
- name: Download SDK
uses: actions/download-artifact@v8
with:
name: CogPackage
path: dist
- name: Download coglet wheel
uses: actions/download-artifact@v8
with:
name: CogletRustWheel
path: dist
- name: Extract source distribution
run: tar xf dist/*.tar.gz --strip-components=1
- uses: jdx/mise-action@v4
with:
cache_key_prefix: mise-ci-${{ github.job }}
- name: Lint Python
run: mise run lint:python
lint-docs:
name: Lint Markdown
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
cache_key_prefix: mise-ci-${{ github.job }}
- name: Lint Markdown
run: mise run lint:docs
# =============================================================================
# Test Jobs
# =============================================================================
test-go:
name: "Test Go (${{ matrix.platform }})"
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
platform: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
cache_key_prefix: mise-ci-${{ github.job }}
- name: Test Go
shell: bash
run: |
set -euo pipefail
set -m # job control, ensures script is in its own process group
cleanup() {
echo "::warning::Cancelling..."
kill -TERM -- -$$ 2>/dev/null || true
sleep 5
kill -KILL -- -$$ 2>/dev/null || true
}
trap cleanup INT TERM
gotestsum -- -short -timeout 1200s -parallel 5 ./... &
wait $!
fuzz-go:
name: Fuzz Go
runs-on: ubuntu-latest
timeout-minutes: 10
env:
CGO_ENABLED: "1"
steps:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
cache_key_prefix: mise-ci-${{ github.job }}
- name: Fuzz schema type resolution
run: go test ./pkg/schema/ -run='^$' -fuzz=FuzzResolveSchemaType -fuzztime=30s
- name: Fuzz JSON schema generation
run: go test ./pkg/schema/ -run='^$' -fuzz=FuzzJSONSchema -fuzztime=30s
- name: Fuzz Python parser
run: go test ./pkg/schema/python/ -run='^$' -fuzz=FuzzParsePredictor -fuzztime=30s
- name: Fuzz type annotation parsing
run: go test ./pkg/schema/python/ -run='^$' -fuzz=FuzzParseTypeAnnotation -fuzztime=30s
test-rust:
name: Test Rust
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
- uses: Swatinem/rust-cache@v2
with:
workspaces: crates -> target
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: jdx/mise-action@v4
with:
cache_key_prefix: mise-ci-${{ github.job }}
- name: Test Rust
run: mise run test:rust
test-python:
name: "Test Python ${{ matrix.python-version }}"
needs: [setup, build-sdk, build-rust]
runs-on: ubuntu-latest-8-cores
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
python-version: ${{ fromJSON(needs.setup.outputs.supported_pythons) }}
steps:
- name: Download artifacts
uses: actions/download-artifact@v8
with:
path: dist
merge-multiple: true
- name: Extract source distribution
run: tar xf dist/*.tar.gz --strip-components=1
- uses: jdx/mise-action@v4
with:
cache_key_prefix: mise-ci-${{ github.job }}
- name: Remove src to ensure tests run against wheel
run: rm -rf python/cog
- name: Test Python
run: uvx nox -s tests -p ${{ matrix.python-version }}
test-coglet-python:
name: "Test coglet-python (${{ matrix.python-version }})"
needs: [setup, build-rust]
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
python-version: ${{ fromJSON(needs.setup.outputs.supported_pythons) }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Download coglet wheel
uses: actions/download-artifact@v8
with:
name: CogletRustWheel
path: dist
- uses: Swatinem/rust-cache@v2
with:
workspaces: crates -> target
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: jdx/mise-action@v4
with:
cache_key_prefix: mise-ci-${{ github.job }}
- name: Test coglet-python bindings
run: uvx nox -s coglet -p ${{ matrix.python-version }}
# Compute integration test shards dynamically.
# Slow tests (tagged with [short] skip) are distributed round-robin first,
# then remaining tests fill in. This ensures slow tests don't pile up on one runner.
integration-shards:
name: Compute test shards
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
shards: ${{ steps.shard.outputs.shards }}
steps:
- uses: actions/checkout@v6
- name: Compute shards
id: shard
run: |
NUM_SHARDS=${{ env.NUM_IT_RUNNER_SHARDS }}
# Find unconditionally skipped tests (bare "skip" without condition brackets)
# These are disabled tests that shouldn't affect shard distribution
SKIPPED_TESTS=$(grep -rl '^skip ' integration-tests/tests/*.txtar | \
xargs -I{} basename {} .txtar | sort || echo "")
# Identify slow tests (have [short] skip marker), excluding unconditionally skipped
SLOW_TESTS=$(grep -rl '\[short\] skip' integration-tests/tests/*.txtar | \
xargs -I{} basename {} .txtar | sort)
if [ -n "$SKIPPED_TESTS" ]; then
SLOW_TESTS=$(comm -23 <(echo "$SLOW_TESTS") <(echo "$SKIPPED_TESTS"))
fi
# All tests
ALL_TESTS=$(ls integration-tests/tests/*.txtar | \
xargs -I{} basename {} .txtar | sort)
# Fast tests = all - slow (skipped tests end up here but run instantly)
FAST_TESTS=$(comm -23 <(echo "$ALL_TESTS") <(echo "$SLOW_TESTS"))
# Distribute slow tests round-robin across shards
declare -a SHARDS
for i in $(seq 0 $((NUM_SHARDS - 1))); do
SHARDS[$i]=""
done
idx=0
while IFS= read -r test; do
[ -z "$test" ] && continue
if [ -n "${SHARDS[$idx]}" ]; then
SHARDS[$idx]="${SHARDS[$idx]}|${test}"
else
SHARDS[$idx]="$test"
fi
idx=$(( (idx + 1) % NUM_SHARDS ))
done <<< "$SLOW_TESTS"
# Distribute fast tests round-robin across shards
while IFS= read -r test; do
[ -z "$test" ] && continue
if [ -n "${SHARDS[$idx]}" ]; then
SHARDS[$idx]="${SHARDS[$idx]}|${test}"
else
SHARDS[$idx]="$test"
fi
idx=$(( (idx + 1) % NUM_SHARDS ))
done <<< "$FAST_TESTS"
# Build JSON array of shard objects
JSON="["
for i in $(seq 0 $((NUM_SHARDS - 1))); do
PATTERN="${SHARDS[$i]}"
COUNT=$(echo "$PATTERN" | tr '|' '\n' | wc -l | tr -d ' ')
[ $i -gt 0 ] && JSON="${JSON},"
JSON="${JSON}{\"index\":$i,\"pattern\":\"${PATTERN}\",\"count\":$COUNT}"
done
JSON="${JSON}]"
echo "shards=$JSON" >> "$GITHUB_OUTPUT"
# Debug output
echo "Shard distribution:"
for i in $(seq 0 $((NUM_SHARDS - 1))); do
COUNT=$(echo "${SHARDS[$i]}" | tr '|' '\n' | wc -l | tr -d ' ')
SLOW_COUNT=$(echo "${SHARDS[$i]}" | tr '|' '\n' | while read t; do
echo "$SLOW_TESTS" | grep -q "^${t}$" && echo "$t"
done | wc -l | tr -d ' ')
echo " Shard $i: $COUNT tests ($SLOW_COUNT slow)"
done
test-integration:
name: "Test integration (shard ${{ matrix.shard.index }})"
needs: [build-cog, build-sdk, build-rust, integration-shards]
runs-on: ubuntu-latest-16-cores
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
shard: ${{ fromJSON(needs.integration-shards.outputs.shards) }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Login to Docker Hub
uses: docker/login-action@v4
if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request'
with:
registry: index.docker.io
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Download artifacts
uses: actions/download-artifact@v8
with:
path: dist
merge-multiple: true
- name: Install cog binary
run: |
cp dist/cog ./cog
chmod +x ./cog
- uses: jdx/mise-action@v4
with:
cache_key_prefix: mise-ci-${{ github.job }}
- name: Set wheel environment
run: |
# Use locally-built wheels, not PyPI (version may not be published yet)
# Must use absolute paths — cog subprocess runs from txtar workdir, not checkout root
echo "COG_SDK_WHEEL=${{ github.workspace }}/dist" >> $GITHUB_ENV
echo "COGLET_WHEEL=${{ github.workspace }}/dist" >> $GITHUB_ENV
- name: Run integration tests (shard ${{ matrix.shard.index }}, ${{ matrix.shard.count }} tests)
env:
COG_BINARY: ./cog
TEST_PARALLEL: 4
BUILDKIT_PROGRESS: 'quiet'
# Resolve cog-base images from GHCR instead of r8.im.
# Images are mirrored by integration-tests/mirror-cog-base-images.sh.
COG_REGISTRY_HOST: 'ghcr.io/replicate/cog'
shell: bash
run: |
set -euo pipefail
set -m # job control, ensures script is in its own process group
cleanup() {
echo "::warning::Cancelling..."
kill -TERM -- -$$ 2>/dev/null || true
sleep 5
kill -KILL -- -$$ 2>/dev/null || true
}
trap cleanup INT TERM
# Build -run regex from shard pattern
# Pattern is "test1|test2|test3" - wrap each in TestIntegration/<name>/
RUN_PATTERN="${{ matrix.shard.pattern }}"
echo "Running tests matching: $RUN_PATTERN"
gotestsum --format github-actions -- \
-tags integration \
-parallel $TEST_PARALLEL \
-timeout 30m \
-run "TestIntegration/($RUN_PATTERN)/" \
./integration-tests/... &
wait $!
# =============================================================================
# Gate Job - Single required check for branch protection
# =============================================================================
ci-complete:
name: CI Complete
needs:
- setup
- version-check
- build-cog
- build-sdk
- build-rust
- fmt-go
- fmt-rust
- fmt-python
- check-llm-docs
- check-stubs
- lint-go
- lint-rust
- lint-rust-deny
- lint-rust-advisories
- lint-python
- lint-docs
- test-go
- fuzz-go
- test-rust
- test-python
- test-coglet-python
- integration-shards
- test-integration
if: always()
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check job results
run: |
echo "Job results:"
echo " version-check: ${{ needs.version-check.result }}"
echo " build-cog: ${{ needs.build-cog.result }}"
echo " build-sdk: ${{ needs.build-sdk.result }}"
echo " build-rust: ${{ needs.build-rust.result }}"
echo " fmt-go: ${{ needs.fmt-go.result }}"
echo " fmt-rust: ${{ needs.fmt-rust.result }}"
echo " fmt-python: ${{ needs.fmt-python.result }}"
echo " check-llm-docs: ${{ needs.check-llm-docs.result }}"
echo " check-stubs: ${{ needs.check-stubs.result }}"
echo " lint-go: ${{ needs.lint-go.result }}"
echo " lint-rust: ${{ needs.lint-rust.result }}"
echo " lint-rust-deny: ${{ needs.lint-rust-deny.result }}"
echo " lint-rust-advisories: ${{ needs.lint-rust-advisories.result }} (informational)"
echo " lint-python: ${{ needs.lint-python.result }}"
echo " lint-docs: ${{ needs.lint-docs.result }}"
echo " test-go: ${{ needs.test-go.result }}"
echo " fuzz-go: ${{ needs.fuzz-go.result }}"
echo " test-rust: ${{ needs.test-rust.result }}"
echo " test-python: ${{ needs.test-python.result }}"
echo " test-coglet-python: ${{ needs.test-coglet-python.result }}"
echo " integration-shards: ${{ needs.integration-shards.result }}"
echo " test-integration: ${{ needs.test-integration.result }}"
# Fail if any required job failed.
# lint-rust-advisories is excluded: it uses continue-on-error because
# advisory DB updates shouldn't block unrelated PRs.
FAILED=false
for result in \
"${{ needs.setup.result }}" \
"${{ needs.version-check.result }}" \
"${{ needs.build-cog.result }}" \
"${{ needs.build-sdk.result }}" \
"${{ needs.build-rust.result }}" \
"${{ needs.fmt-go.result }}" \
"${{ needs.fmt-rust.result }}" \
"${{ needs.fmt-python.result }}" \
"${{ needs.check-llm-docs.result }}" \
"${{ needs.check-stubs.result }}" \
"${{ needs.lint-go.result }}" \
"${{ needs.lint-rust.result }}" \
"${{ needs.lint-rust-deny.result }}" \
"${{ needs.lint-python.result }}" \
"${{ needs.lint-docs.result }}" \
"${{ needs.test-go.result }}" \
"${{ needs.fuzz-go.result }}" \
"${{ needs.test-rust.result }}" \
"${{ needs.test-python.result }}" \
"${{ needs.test-coglet-python.result }}" \
"${{ needs.integration-shards.result }}" \
"${{ needs.test-integration.result }}"
do
if [ "$result" = "failure" ] || [ "$result" = "cancelled" ]; then
FAILED=true
fi
done
if [ "$FAILED" = "true" ]; then
echo "::error::Some jobs failed or were cancelled"
exit 1
fi
echo "All CI checks passed!"
# =============================================================================
# Release Validation - Dry-run checks (PRs and main)
# =============================================================================
release-dry-run:
name: Release Dry Run
needs: ci-complete
if: "!startsWith(github.ref, 'refs/tags/')"
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: Swatinem/rust-cache@v2
with:
workspaces: crates -> target
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: jdx/mise-action@v4
with:
cache_key_prefix: mise-ci-${{ github.job }}
- name: Check coglet crates.io publish
run: cargo publish --dry-run -p coglet --manifest-path crates/Cargo.toml
- uses: goreleaser/goreleaser-action@v7
with:
version: '~> v2'
args: check