Skip to content

Bump actions/checkout from 6 to 7 (#555) #1380

Bump actions/checkout from 6 to 7 (#555)

Bump actions/checkout from 6 to 7 (#555) #1380

Workflow file for this run

name: CI
on:
push:
branches: [main]
paths-ignore:
- "docs-cms/**"
- "docusaurus/**"
- "tooling/build_docs.py"
- "tooling/validate_docs.py"
- "**/*.md"
- ".github/workflows/docs.yml"
pull_request:
branches: [main]
paths-ignore:
- "docs-cms/**"
- "docusaurus/**"
- "tooling/build_docs.py"
- "tooling/validate_docs.py"
- "**/*.md"
- ".github/workflows/docs.yml"
merge_group:
types: [checks_requested]
# Cancel in-progress runs for the same PR/branch/merge group
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.merge_group.head_sha || github.ref }}
cancel-in-progress: true
jobs:
# Job 0: Generate CI matrix (selective execution - RFC-045)
generate-matrix:
name: Generate CI Matrix
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
matrix: ${{ steps.matrix.outputs.matrix }}
has_test: ${{ steps.matrix.outputs.has_test }}
run_full: ${{ steps.check-label.outputs.run_full }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0 # Need full history for git diff
- name: Check for ci:full label
id: check-label
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
LABELS=$(gh pr view ${{ github.event.pull_request.number }} --json labels --jq '.labels[].name')
if echo "$LABELS" | grep -q "ci:full"; then
echo "run_full=true" >> "$GITHUB_OUTPUT"
echo "✓ ci:full label detected - running full CI"
else
echo "run_full=false" >> "$GITHUB_OUTPUT"
fi
else
echo "run_full=false" >> "$GITHUB_OUTPUT"
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "latest"
enable-cache: true
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Generate selective CI matrix
id: matrix
run: |
if [ "${{ steps.check-label.outputs.run_full }}" = "true" ]; then
# Full CI requested - set outputs to indicate all jobs should run
{
echo "matrix={}"
echo "has_test=true"
echo "has_lint=true"
echo "has_build=true"
echo "has_docs=true"
} >> "$GITHUB_OUTPUT"
exit 0
fi
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE_REF="${{ github.event.pull_request.base.sha }}"
HEAD_REF="${{ github.event.pull_request.head.sha }}"
elif [ "${{ github.event_name }}" = "push" ]; then
# For push events, compare current commit against previous commit
BASE_REF="${{ github.event.before }}"
HEAD_REF="${{ github.event.after }}"
else
# For other events (merge_group, etc.), compare against main
BASE_REF="origin/main"
HEAD_REF="HEAD"
fi
echo "📊 Generating selective CI matrix..."
echo "Event: ${{ github.event_name }}"
echo "Base: $BASE_REF"
echo "Head: $HEAD_REF"
uv run tooling/ci_matrix.py \
--mode=github-actions \
--base="$BASE_REF" \
--head="$HEAD_REF"
- name: Display CI plan
if: steps.check-label.outputs.run_full != 'true'
run: |
{
echo "## 📋 Selective CI Execution Plan"
echo ""
echo "### Matrix Outputs"
echo ""
echo "| Output | Value |"
echo "|--------|-------|"
echo "| \`run_full\` | \`${{ steps.check-label.outputs.run_full }}\` |"
echo "| \`has_test\` | \`${{ steps.matrix.outputs.has_test }}\` |"
echo "| \`matrix\` | \`${{ steps.matrix.outputs.matrix }}\` |"
echo ""
echo "### Execution Summary"
echo ""
if [ "${{ steps.matrix.outputs.has_test }}" = "true" ]; then
echo "✅ **Tests**: Will run affected test suites"
echo ""
echo "> Matrix jobs will execute with proper names (e.g., 'Test MemStore Driver')"
else
echo "⏭️ **Tests**: Skipped (no test-related changes)"
echo ""
echo "> Matrix jobs will run but skip actual test execution"
fi
echo ""
echo "💡 Add \`ci:full\` label to run complete CI pipeline"
} >> "$GITHUB_STEP_SUMMARY"
- name: Display full CI notice
if: steps.check-label.outputs.run_full == 'true'
run: |
{
echo "## 🔄 Full CI Execution"
echo ""
echo "### Matrix Outputs"
echo ""
echo "| Output | Value |"
echo "|--------|-------|"
echo "| \`run_full\` | \`true\` |"
echo "| \`has_test\` | \`${{ steps.matrix.outputs.has_test }}\` |"
echo ""
echo "✅ Running complete CI pipeline (ci:full label detected)"
echo ""
echo "> All matrix jobs will execute with full test coverage"
} >> "$GITHUB_STEP_SUMMARY"
# Job 1: Generate protobuf code once (new)
generate-proto:
name: Generate Protobuf Code
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version: "1.25.4"
cache: false # We'll use custom cache for more control
- name: Cache Go dependencies and build cache
uses: actions/cache@v5
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-proto-${{ hashFiles('**/go.sum', '**/go.mod') }}
restore-keys: |
${{ runner.os }}-go-proto-
${{ runner.os }}-go-
# Note: No protoc needed - buf uses remote plugins (buf.build/protocolbuffers/go)
- name: Install buf
uses: bufbuild/buf-setup-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
buf_api_token: ${{ secrets.BUF_BSR_KEY }}
buf_user: jrepp
- name: Install Task
run: |
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin
- name: Generate protobuf code
run: task proto-go
- name: Upload generated proto code
uses: actions/upload-artifact@v7
with:
name: proto-generated
path: pkg/plugin/gen/
retention-days: 1
# Job 1: Lint Rust code
lint-rust:
name: Lint Rust
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Install protoc
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
- name: Cache Rust dependencies
uses: actions/cache@v5
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
prism-proxy/target/
key: ${{ runner.os }}-cargo-${{ hashFiles('prism-proxy/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Format Rust code
run: cd prism-proxy && cargo fmt --all -- --check
- name: Lint Rust with Clippy
run: cd prism-proxy && cargo clippy --all-targets --all-features -- -D warnings
# Job 2: Lint Python code
lint-python:
name: Lint Python
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "latest"
enable-cache: true
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Lint Python with ruff
run: |
uv run ruff check tooling/
uv run ruff format --check tooling/
# Job 2b: Lint Protobuf files
lint-proto:
name: Lint Protobuf
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0 # Need full history for breaking change detection
- name: Install buf
uses: bufbuild/buf-setup-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
buf_api_token: ${{ secrets.BUF_BSR_KEY }}
buf_user: jrepp
- name: Lint protobuf files
run: cd proto && buf lint
- name: Check protobuf formatting
run: cd proto && buf format --diff --exit-code
- name: Check for breaking-change label
id: check-breaking-label
if: github.event_name == 'pull_request'
run: |
LABELS=$(gh pr view ${{ github.event.pull_request.number }} --json labels --jq '.labels[].name')
if echo "$LABELS" | grep -q "breaking-change"; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "⚠️ breaking-change label detected - skipping breaking changes check"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check for breaking changes
if: github.event_name == 'pull_request' && steps.check-breaking-label.outputs.skip != 'true'
run: |
cd proto
buf breaking --against "../.git#branch=origin/main,subdir=proto"
- name: Skip breaking changes check notice
if: github.event_name == 'pull_request' && steps.check-breaking-label.outputs.skip == 'true'
run: |
echo "::notice::Breaking changes check skipped due to breaking-change label"
# Job 2c: Lint GitHub Actions workflows
lint-github-actions:
name: Lint GitHub Actions
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Run actionlint
uses: reviewdog/action-actionlint@v1
with:
reporter: github-check
level: error
fail_level: error
# Job 3: Lint Go code (parallel by category)
lint-go:
name: Lint Go (${{ matrix.category }})
runs-on: ubuntu-latest
timeout-minutes: 15
needs: generate-proto
strategy:
fail-fast: true # Cancel all on first failure for faster feedback
matrix:
category: [critical, security, style, quality]
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version: "1.25.4"
cache: false # We'll use custom cache for more control
- name: Cache Go dependencies and build cache
uses: actions/cache@v5
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-lint-${{ hashFiles('**/go.sum', '**/go.mod') }}
restore-keys: |
${{ runner.os }}-go-lint-
${{ runner.os }}-go-
- name: Download generated proto code
uses: actions/download-artifact@v8
with:
name: proto-generated
path: pkg/plugin/gen/
- name: Cache golangci-lint binary
id: cache-golangci-lint
uses: actions/cache@v5
with:
path: ~/go/bin/golangci-lint
key: ${{ runner.os }}-golangci-lint-binary-v2.5.0
restore-keys: |
${{ runner.os }}-golangci-lint-binary-
- name: Install golangci-lint
if: steps.cache-golangci-lint.outputs.cache-hit != 'true'
run: |
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \
| sh -s -- -b "$(go env GOPATH)/bin" v2.5.0
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "latest"
enable-cache: true
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Cache golangci-lint
uses: actions/cache@v5
with:
path: ~/.cache/golangci-lint
key: ${{ runner.os }}-golangci-lint-${{ matrix.category }}-${{ hashFiles('.golangci.yml') }}
restore-keys: |
${{ runner.os }}-golangci-lint-${{ matrix.category }}-
- name: Lint Go - ${{ matrix.category }}
run: |
uv run tooling/parallel_lint.py \
--categories ${{ matrix.category }} \
--fail-fast
# Build Jobs (run early in parallel after linting passes)
build-rust:
name: Build Rust Components
runs-on: ubuntu-latest
timeout-minutes: 10
needs: lint-rust
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Install protoc
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
- name: Cache Rust dependencies
uses: actions/cache@v5
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
prism-proxy/target/
key: ${{ runner.os }}-cargo-build-${{ hashFiles('prism-proxy/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-build-
${{ runner.os }}-cargo-
- name: Install Task
run: |
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin
- name: Build Rust proxy
run: task proxy
- name: Upload Rust binaries
uses: actions/upload-artifact@v7
with:
name: rust-binaries
path: prism-proxy/target/release/prism-proxy
retention-days: 7
build-go:
name: Build Go Components
runs-on: ubuntu-latest
timeout-minutes: 15
needs: [generate-proto, lint-go]
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version: "1.25.4"
cache: false
- name: Cache Go dependencies and build cache
uses: actions/cache@v5
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum', '**/go.mod') }}
restore-keys: |
${{ runner.os }}-go-build-
${{ runner.os }}-go-
- name: Download generated proto code
uses: actions/download-artifact@v8
with:
name: proto-generated
path: pkg/plugin/gen/
- name: Install Task
run: |
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin
- name: Build Go components
run: |
task prismctl
task prism-admin
task prism-loadtest
task prism-launcher
task plugin-watcher
task patterns
- name: Upload Go binaries
uses: actions/upload-artifact@v7
with:
name: go-binaries
path: |
build/prismctl
build/prism-admin
build/prism-loadtest
build/prism-launcher
build/plugin-watcher
build/*-runner
retention-days: 7
# Job 2: Test Rust proxy (unit and integration tests)
# Note: Job always runs to provide visibility. Steps skip when Rust tests aren't needed.
test-proxy:
name: Test Rust Proxy
runs-on: ubuntu-latest
timeout-minutes: 15
needs: [generate-matrix]
# No job-level 'if' - provides consistent visibility in CI UI
steps:
- name: Check if Rust tests should run
id: should-run
run: |
# Debug: Show execution decision
{
echo "## Rust Proxy Tests Configuration"
echo ""
echo "| Input | Value |"
echo "|-------|-------|"
echo "| run_full | \`${{ needs.generate-matrix.outputs.run_full }}\` |"
echo "| has_test | \`${{ needs.generate-matrix.outputs.has_test }}\` |"
echo ""
} >> "$GITHUB_STEP_SUMMARY"
# Rust tests run when full CI or any tests are triggered
# (prism-proxy changes will set has_test=true via test:unit-proxy)
if [[ "${{ needs.generate-matrix.outputs.run_full }}" == "true" ]] || \
[[ "${{ needs.generate-matrix.outputs.has_test }}" == "true" ]]; then
echo "run=true" >> "$GITHUB_OUTPUT"
echo "**Decision:** Running Rust proxy tests" >> "$GITHUB_STEP_SUMMARY"
else
echo "run=false" >> "$GITHUB_OUTPUT"
echo "**Decision:** Skipping Rust tests (no test-related changes detected)" >> "$GITHUB_STEP_SUMMARY"
echo "::notice::Skipping Rust proxy tests - no test-related changes detected"
fi
- name: Checkout
if: steps.should-run.outputs.run == 'true'
uses: actions/checkout@v7
- name: Setup Rust
if: steps.should-run.outputs.run == 'true'
uses: dtolnay/rust-toolchain@stable
- name: Install protoc
if: steps.should-run.outputs.run == 'true'
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
- name: Cache Rust dependencies
if: steps.should-run.outputs.run == 'true'
uses: actions/cache@v5
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
prism-proxy/target/
key: ${{ runner.os }}-cargo-test-${{ hashFiles('prism-proxy/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-test-
${{ runner.os }}-cargo-
- name: Cache cargo-tarpaulin
if: steps.should-run.outputs.run == 'true'
id: cache-tarpaulin
uses: actions/cache@v5
with:
path: ~/.cargo/bin/cargo-tarpaulin
key: ${{ runner.os }}-cargo-tarpaulin-0.27.0
- name: Install cargo-tarpaulin
if: steps.should-run.outputs.run == 'true' && steps.cache-tarpaulin.outputs.cache-hit != 'true'
run: cargo install cargo-tarpaulin --version 0.27.0
- name: Run Rust unit tests with coverage
if: steps.should-run.outputs.run == 'true'
run: |
mkdir -p build/coverage-reports/coverage-rust
cd prism-proxy && cargo tarpaulin --lib --verbose --out xml --output-dir ../build/coverage-reports/coverage-rust
- name: Run Rust integration tests
if: steps.should-run.outputs.run == 'true'
run: |
echo "Running Rust integration tests..."
cd prism-proxy && cargo test --test integration_test -- --ignored --nocapture || echo "::warning::Rust integration tests failed or were skipped"
echo "Rust integration tests complete"
- name: Upload Rust coverage
if: steps.should-run.outputs.run == 'true'
uses: actions/upload-artifact@v7
with:
name: coverage-rust
path: build/coverage-reports/coverage-rust/cobertura.xml
retention-days: 7
if-no-files-found: error
# Job 3: Test Go drivers and patterns (including acceptance tests)
# Note: Job always runs to ensure matrix.name is properly displayed in UI.
# Steps use conditions to skip actual work when tests aren't needed.
test-patterns:
name: Test ${{ matrix.name }}
runs-on: ubuntu-latest
timeout-minutes: 15
needs: [generate-matrix, generate-proto]
# No job-level 'if' - this ensures matrix.name is properly expanded in job names
strategy:
fail-fast: false
matrix:
include:
# Unit tests for drivers
- name: "MemStore Driver"
path: "pkg/drivers/memstore"
artifact: "memstore"
type: "unit"
- name: "Redis Driver"
path: "pkg/drivers/redis"
artifact: "redis"
type: "unit"
- name: "NATS Driver"
path: "pkg/drivers/nats"
artifact: "nats"
type: "unit"
# Unit tests for patterns
- name: "Consumer Pattern"
path: "patterns/consumer"
artifact: "consumer"
type: "unit"
- name: "Producer Pattern"
path: "patterns/producer"
artifact: "producer"
type: "unit"
- name: "Multicast Registry Pattern"
path: "patterns/multicast_registry"
artifact: "multicast-registry"
type: "unit"
- name: "KeyValue Pattern"
path: "patterns/keyvalue"
artifact: "keyvalue"
type: "unit"
- name: "Mailbox Pattern"
path: "patterns/mailbox"
artifact: "mailbox"
type: "unit"
- name: "Inference Pattern"
path: "patterns/inference/cmd/inference-runner"
artifact: "inference"
type: "unit"
# Acceptance tests for patterns
- name: "KeyValue Acceptance"
path: "tests/acceptance/patterns/keyvalue"
artifact: "acceptance-keyvalue"
type: "acceptance"
pattern_dir: "patterns/keyvalue"
- name: "Consumer Acceptance"
path: "tests/acceptance/patterns/consumer"
artifact: "acceptance-consumer"
type: "acceptance"
pattern_dir: "patterns/consumer"
- name: "Producer Acceptance"
path: "tests/acceptance/patterns/producer"
artifact: "acceptance-producer"
type: "acceptance"
pattern_dir: "patterns/producer"
- name: "ClaimCheck Acceptance"
path: "tests/acceptance/patterns/claimcheck"
artifact: "acceptance-claimcheck"
type: "acceptance"
pattern_dir: "patterns/claimcheck"
- name: "Unified Acceptance"
path: "tests/acceptance/patterns/unified"
artifact: "acceptance-unified"
type: "acceptance"
pattern_dir: "patterns/unified"
- name: "Mailbox Acceptance"
path: "tests/acceptance/patterns/mailbox"
artifact: "acceptance-mailbox"
type: "acceptance"
pattern_dir: "patterns/mailbox"
steps:
- name: Check if tests should run
id: should-run
run: |
# Debug: Show matrix expansion worked correctly
{
echo "## Matrix Configuration"
echo ""
echo "| Property | Value |"
echo "|----------|-------|"
echo "| **Name** | ${{ matrix.name }} |"
echo "| **Path** | \`${{ matrix.path }}\` |"
echo "| **Type** | ${{ matrix.type }} |"
echo "| **Artifact** | ${{ matrix.artifact }} |"
} >> "$GITHUB_STEP_SUMMARY"
if [[ -n "${{ matrix.pattern_dir }}" ]]; then
echo "| **Pattern Dir** | \`${{ matrix.pattern_dir }}\` |" >> "$GITHUB_STEP_SUMMARY"
fi
echo "" >> "$GITHUB_STEP_SUMMARY"
# Determine if tests should run
{
echo "## Execution Decision"
echo ""
echo "| Input | Value |"
echo "|-------|-------|"
echo "| run_full | \`${{ needs.generate-matrix.outputs.run_full }}\` |"
echo "| has_test | \`${{ needs.generate-matrix.outputs.has_test }}\` |"
echo ""
} >> "$GITHUB_STEP_SUMMARY"
if [[ "${{ needs.generate-matrix.outputs.run_full }}" == "true" ]] || \
[[ "${{ needs.generate-matrix.outputs.has_test }}" == "true" ]]; then
echo "run=true" >> "$GITHUB_OUTPUT"
echo "**Decision:** Running tests" >> "$GITHUB_STEP_SUMMARY"
else
echo "run=false" >> "$GITHUB_OUTPUT"
echo "**Decision:** Skipping tests (no test-related changes detected)" >> "$GITHUB_STEP_SUMMARY"
echo "::notice::Skipping tests for ${{ matrix.name }} - no test-related changes detected"
fi
- name: Checkout
if: steps.should-run.outputs.run == 'true'
uses: actions/checkout@v7
- name: Setup Go
if: steps.should-run.outputs.run == 'true'
uses: actions/setup-go@v6
with:
go-version: "1.25.4"
cache: false # We'll use custom cache for more control
- name: Download generated proto code
if: steps.should-run.outputs.run == 'true'
uses: actions/download-artifact@v8
with:
name: proto-generated
path: pkg/plugin/gen/
- name: Cache Go dependencies and build cache
if: steps.should-run.outputs.run == 'true'
uses: actions/cache@v5
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-test-${{ hashFiles('**/go.sum', '**/go.mod') }}
restore-keys: |
${{ runner.os }}-go-test-
${{ runner.os }}-go-
- name: Download dependencies for acceptance tests
if: steps.should-run.outputs.run == 'true' && matrix.type == 'acceptance' && matrix.pattern_dir != ''
run: |
if [ -d "${{ matrix.pattern_dir }}" ]; then
cd ${{ matrix.pattern_dir }}
go mod download
fi
- name: Build pattern module for acceptance tests
if: steps.should-run.outputs.run == 'true' && matrix.type == 'acceptance' && matrix.pattern_dir != ''
run: |
if [ -d "${{ matrix.pattern_dir }}" ]; then
cd ${{ matrix.pattern_dir }}
go build ./...
fi
- name: Run tests with coverage for ${{ matrix.name }}
if: steps.should-run.outputs.run == 'true'
run: |
cd ${{ matrix.path }}
go test -v -race -coverprofile=coverage.out -covermode=atomic -timeout 15m ./...
go tool cover -func=coverage.out | grep total | awk '{print "Coverage: " $3}'
env:
PRISM_TEST_QUIET: "1"
# Skip testcontainers backends with known driver bugs
# TODO: Remove once NATS metadata preservation and SQLite GetEvent are fixed
PRISM_SKIP_CONTAINERS: "1"
- name: Upload coverage
if: steps.should-run.outputs.run == 'true'
uses: actions/upload-artifact@v7
with:
name: coverage-${{ matrix.artifact }}
path: ${{ matrix.path }}/coverage.out
retention-days: 7
# Job 4: Integration tests
# Note: Job always runs to provide visibility. Steps skip when tests aren't needed.
test-integration:
name: Integration Tests
runs-on: ubuntu-latest
timeout-minutes: 10
needs: [generate-matrix, generate-proto]
# No job-level 'if' - provides consistent visibility in CI UI
steps:
- name: Check if integration tests should run
id: should-run
run: |
# Debug: Show execution decision
{
echo "## Integration Tests Configuration"
echo ""
echo "| Input | Value |"
echo "|-------|-------|"
echo "| run_full | \`${{ needs.generate-matrix.outputs.run_full }}\` |"
echo "| has_test | \`${{ needs.generate-matrix.outputs.has_test }}\` |"
echo ""
} >> "$GITHUB_STEP_SUMMARY"
if [[ "${{ needs.generate-matrix.outputs.run_full }}" == "true" ]] || \
[[ "${{ needs.generate-matrix.outputs.has_test }}" == "true" ]]; then
echo "run=true" >> "$GITHUB_OUTPUT"
echo "**Decision:** Running integration tests" >> "$GITHUB_STEP_SUMMARY"
else
echo "run=false" >> "$GITHUB_OUTPUT"
echo "**Decision:** Skipping integration tests (no test-related changes detected)" >> "$GITHUB_STEP_SUMMARY"
echo "::notice::Skipping integration tests - no test-related changes detected"
fi
- name: Checkout
if: steps.should-run.outputs.run == 'true'
uses: actions/checkout@v7
- name: Setup Go
if: steps.should-run.outputs.run == 'true'
uses: actions/setup-go@v6
with:
go-version: "1.25.4"
cache: false # We'll use custom cache for more control
- name: Cache Go dependencies and build cache
if: steps.should-run.outputs.run == 'true'
uses: actions/cache@v5
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-integration-${{ hashFiles('**/go.sum', '**/go.mod') }}
restore-keys: |
${{ runner.os }}-go-integration-
${{ runner.os }}-go-
- name: Download generated proto code
if: steps.should-run.outputs.run == 'true'
uses: actions/download-artifact@v8
with:
name: proto-generated
path: pkg/plugin/gen/
- name: Run integration tests with coverage
if: steps.should-run.outputs.run == 'true'
run: |
cd tests/integration
go test -v -race -coverprofile=coverage.out -covermode=atomic -timeout 5m ./...
go tool cover -func=coverage.out | grep total | awk '{print "Integration Coverage: " $3}'
- name: Upload coverage
if: steps.should-run.outputs.run == 'true'
uses: actions/upload-artifact@v7
with:
name: coverage-integration
path: tests/integration/coverage.out
retention-days: 7
# Job 6: Validate documentation
validate-docs:
name: Validate Documentation
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: "20"
cache: "npm"
cache-dependency-path: docusaurus/package-lock.json
- name: Install Node dependencies
run: cd docusaurus && npm ci
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "latest"
enable-cache: true
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Validate documentation
run: uv run tooling/validate_docs.py
# Job 7: Build all components
# Unified build status (skipped, parallel builds above replace this)
build:
name: Build All Components
runs-on: ubuntu-latest
timeout-minutes: 5
needs: [build-rust, build-go, test-proxy, test-patterns, test-integration]
if: always()
steps:
- name: Check build status
run: |
if [ "${{ needs.build-rust.result }}" != "success" ]; then
echo "❌ Rust build failed"
exit 1
fi
if [ "${{ needs.build-go.result }}" != "success" ]; then
echo "❌ Go build failed"
exit 1
fi
echo "✅ All builds passed"
# Job 8: Coverage summary
coverage-summary:
name: Coverage Summary
runs-on: ubuntu-latest
timeout-minutes: 5
needs: [test-proxy, test-patterns, test-integration]
if: always()
steps:
- name: Download all coverage reports
uses: actions/download-artifact@v8
with:
pattern: coverage-*
path: build/coverage-reports
- name: Display coverage summary
run: |
{
echo "## Coverage Summary"
echo ""
echo "| Component | Coverage Files |"
echo "|-----------|----------------|"
for dir in build/coverage-reports/*/; do
component=$(basename "$dir")
if [ -f "$dir/coverage.out" ]; then
echo "| $component | ✅ Present |"
else
echo "| $component | ❌ Missing |"
fi
done
} >> "$GITHUB_STEP_SUMMARY"
# Job 9: Upload to Codecov
codecov-upload:
name: Upload Coverage to Codecov
runs-on: ubuntu-latest
timeout-minutes: 10
needs: [test-proxy, test-patterns, test-integration]
if: always()
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Download all coverage reports
uses: actions/download-artifact@v8
with:
pattern: coverage-*
path: build/coverage-reports
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v7
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: >-
build/coverage-reports/coverage-rust/cobertura.xml,
build/coverage-reports/coverage-memstore/coverage.out,
build/coverage-reports/coverage-redis/coverage.out,
build/coverage-reports/coverage-nats/coverage.out,
build/coverage-reports/coverage-consumer/coverage.out,
build/coverage-reports/coverage-producer/coverage.out,
build/coverage-reports/coverage-multicast-registry/coverage.out,
build/coverage-reports/coverage-keyvalue/coverage.out,
build/coverage-reports/coverage-mailbox/coverage.out,
build/coverage-reports/coverage-inference/coverage.out,
build/coverage-reports/coverage-integration/coverage.out,
build/coverage-reports/coverage-acceptance-keyvalue/coverage.out,
build/coverage-reports/coverage-acceptance-consumer/coverage.out,
build/coverage-reports/coverage-acceptance-producer/coverage.out,
build/coverage-reports/coverage-acceptance-claimcheck/coverage.out,
build/coverage-reports/coverage-acceptance-unified/coverage.out,
build/coverage-reports/coverage-acceptance-mailbox/coverage.out
flags: unittests,integration,acceptance
name: prism-coverage
fail_ci_if_error: false
verbose: true
# Job 10: CI status check (required for merge)
ci-status:
name: CI Status Check
runs-on: ubuntu-latest
timeout-minutes: 5
needs:
[
lint-rust,
lint-python,
lint-proto,
lint-github-actions,
lint-go,
test-proxy,
test-patterns,
test-integration,
validate-docs,
build,
]
if: always()
steps:
- name: Check all jobs status
id: ci_check
run: |
# Accept both "success" and "skipped" as passing states
check_job() {
local result="$1"
[[ "$result" == "success" ]] || [[ "$result" == "skipped" ]]
}
if ! check_job "${{ needs.lint-rust.result }}" || \
! check_job "${{ needs.lint-python.result }}" || \
! check_job "${{ needs.lint-proto.result }}" || \
! check_job "${{ needs.lint-github-actions.result }}" || \
! check_job "${{ needs.lint-go.result }}" || \
! check_job "${{ needs.test-proxy.result }}" || \
! check_job "${{ needs.test-patterns.result }}" || \
! check_job "${{ needs.test-integration.result }}" || \
! check_job "${{ needs.validate-docs.result }}" || \
! check_job "${{ needs.build.result }}"; then
echo "❌ CI pipeline failed"
echo "status=failure" >> "$GITHUB_OUTPUT"
exit 1
else
echo "✅ CI pipeline passed"
echo "status=success" >> "$GITHUB_OUTPUT"
fi
- name: send-ntfy-notification
if: always() && vars.NTFY_TOPIC != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_BODY: ${{ github.event.pull_request.body }}
run: |
STATUS_EMOJI="${{ steps.ci_check.outputs.status == 'success' && '✅' || '❌' }}"
STATUS_TEXT="${{ steps.ci_check.outputs.status == 'success' && 'Passed' || 'Failed' }}"
PRIORITY="${{ steps.ci_check.outputs.status == 'success' && 'default' || 'high' }}"
TAG="${{ steps.ci_check.outputs.status == 'success' && 'white_check_mark' || 'x' }}"
# Get PR information if this is a PR
if [ "${{ github.event_name }}" = "pull_request" ]; then
PR_NUMBER="${{ github.event.pull_request.number }}"
# Extract first 2-3 lines of PR description (safely handle special chars)
PR_DESC_SHORT=$(printf '%s\n' "$PR_BODY" | head -3 | sed 's/^/ /')
# Get list of changed files for summary
CHANGED_FILES=$(gh pr view "$PR_NUMBER" --repo "${{ github.repository }}" --json files --jq '.files[].path' | head -5)
FILE_COUNT=$(gh pr view "$PR_NUMBER" --repo "${{ github.repository }}" --json files --jq '.files | length')
# Build changed files summary
if [ "$FILE_COUNT" -gt 0 ]; then
CHANGES_SUMMARY="Changed Files ($FILE_COUNT total):"
CHANGES_SUMMARY="$CHANGES_SUMMARY
$(echo "$CHANGED_FILES" | head -5 | sed 's/^/ • /')"
if [ "$FILE_COUNT" -gt 5 ]; then
CHANGES_SUMMARY="$CHANGES_SUMMARY
... and $((FILE_COUNT - 5)) more"
fi
else
CHANGES_SUMMARY="No files changed"
fi
PR_INFO="PR #${PR_NUMBER}: ${PR_TITLE}
Description:
${PR_DESC_SHORT}
${CHANGES_SUMMARY}
"
else
PR_INFO="Push to ${{ github.ref_name }}"
fi
# Build job results summary
JOBS="Lint Rust: ${{ needs.lint-rust.result == 'success' && '✅' || '❌' }}
Lint Python: ${{ needs.lint-python.result == 'success' && '✅' || '❌' }}
Lint Protobuf: ${{ needs.lint-proto.result == 'success' && '✅' || '❌' }}
Lint GitHub Actions: ${{ needs.lint-github-actions.result == 'success' && '✅' || '❌' }}
Lint Go: ${{ needs.lint-go.result == 'success' && '✅' || '❌' }}
Test Proxy: ${{ needs.test-proxy.result == 'success' && '✅' || '❌' }}
Test Patterns: ${{ needs.test-patterns.result == 'success' && '✅' || '❌' }}
Test Integration: ${{ needs.test-integration.result == 'success' && '✅' || '❌' }}
Validate Docs: ${{ needs.validate-docs.result == 'success' && '✅' || '❌' }}
Build: ${{ needs.build.result == 'success' && '✅' || '❌' }}"
curl -H "Title: ${STATUS_EMOJI} CI Pipeline ${STATUS_TEXT}" \
-H "Priority: ${PRIORITY}" \
-H "Tags: ${TAG},ci" \
-H "Click: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
-d "${PR_INFO}
Repository: ${{ github.repository }}
Branch: ${{ github.ref_name }}
Commit: ${GITHUB_SHA:0:7}
Author: ${{ github.actor }}
Job Results:
${JOBS}" \
https://ntfy.sh/${{ vars.NTFY_TOPIC }}