Skip to content

web: status page reads Control Plane's event-sourced feed #609

web: status page reads Control Plane's event-sourced feed

web: status page reads Control Plane's event-sourced feed #609

Workflow file for this run

name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
FORCE_COLOR: 1
jobs:
# ============================================================================
# FAST CHECKS - Run first to fail fast on simple issues
# ============================================================================
# Validate workflow syntax before running expensive operations
workflow-lint:
name: Workflow Syntax
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install actionlint
run: |
# Download actionlint from GitHub releases
ACTIONLINT_VERSION="1.7.7"
curl -sLO "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"
tar xzf "actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"
chmod +x actionlint
- name: Lint workflow files
run: |
# Ignore shellcheck warnings that are false positives for GitHub Actions:
# SC2086: Word splitting - often intentional with GHA outputs
# SC2129: Multiple echo redirects - style preference
# SC2193: Comparison can never be equal - false positive for GHA template syntax
./actionlint -ignore 'SC2086' -ignore 'SC2129' -ignore 'SC2193' -color
# Validate workflow structure with act (dry-run)
workflow-validate:
name: Workflow Validation (act)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install act
run: |
curl -s https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash -s -- -b /usr/local/bin
- name: Validate workflow structure
run: |
echo "Validating workflow job graphs..."
for workflow in .github/workflows/*.yml; do
echo "Checking $(basename "$workflow")..."
act --list -W "$workflow" || {
echo "Warning: Could not parse $workflow - may have dynamic elements"
}
done
- name: Validate ci.yml with dry-run
run: |
echo "Running dry-run validation on ci.yml..."
# Dry-run the workflow-lint job to validate the workflow structure
# This catches issues that actionlint might miss (invalid expressions, etc.)
act push --dryrun -W .github/workflows/ci.yml --job workflow-lint 2>&1 || {
echo "Note: Dry-run may show warnings for missing secrets/env vars - this is expected"
}
version-check:
name: Version Consistency
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Check version consistency
run: |
chmod +x scripts/check-versions.sh
./scripts/check-versions.sh
# ============================================================================
# Change Detection - Determine which services need to be tested
# ============================================================================
changes:
name: Detect Changes
runs-on: ubuntu-latest
outputs:
rust: ${{ steps.filter.outputs.rust }}
go: ${{ steps.filter.outputs.go }}
web: ${{ steps.filter.outputs.web }}
elixir: ${{ steps.filter.outputs.elixir }}
mcp: ${{ steps.filter.outputs.mcp }}
prime: ${{ steps.filter.outputs.prime }}
workflows: ${{ steps.filter.outputs.workflows }}
# Container-related changes for downstream workflows
core: ${{ steps.filter.outputs.rust }}
query-service: ${{ steps.filter.outputs.elixir }}
control-plane: ${{ steps.filter.outputs.go }}
steps:
- uses: actions/checkout@v6
- uses: dorny/paths-filter@v4
id: filter
with:
filters: |
rust:
- 'apps/core/**'
- 'Cargo.lock'
go:
- 'apps/control-plane/**'
web:
- 'apps/web/**'
- 'packages/ui/**'
- 'package.json'
- 'bun.lock'
elixir:
- 'apps/query-service/**'
mcp:
- 'apps/mcp-server-elixir/**'
prime:
- 'apps/prime-mcp/**'
- 'apps/core/src/prime/**'
- 'tooling/recall-bench/**'
workflows:
- '.github/workflows/**'
# ============================================================================
# Rust Quality Gates (Core Service)
# ============================================================================
rust-quality:
name: Rust Quality Gates
needs: [workflow-lint, workflow-validate, version-check, changes]
if: needs.changes.outputs.rust == 'true' || needs.changes.outputs.workflows == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: apps/core
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@nightly
with:
components: rustfmt, clippy
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: "apps/core -> target"
cache-on-failure: true
- name: Install cargo-sort
uses: taiki-e/install-action@v2
with:
tool: cargo-sort
# Fast checks first
- name: Check formatting
run: cargo fmt --check
- name: Check Cargo.toml sorting
run: cargo sort --check
# Then linting
- name: Run Clippy
run: cargo clippy --locked --all-targets --all-features -- -D warnings
# Then tests and build (both editions)
- name: Run tests (enterprise — all features)
run: cargo test --locked --lib --all-features
- name: Run tests (community edition)
run: cargo test --locked --lib --features community
- name: Build release
run: cargo build --locked --lib --release
- name: Check documentation
run: cargo doc --no-deps --document-private-items
env:
RUSTDOCFLAGS: "-D warnings"
# ============================================================================
# Prime MCP Quality Gates
# ============================================================================
prime-quality:
name: Prime MCP Quality Gates
needs: [workflow-lint, workflow-validate, version-check, changes]
if: needs.changes.outputs.prime == 'true' || needs.changes.outputs.workflows == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@nightly
with:
components: rustfmt, clippy
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: "apps/prime-mcp -> target"
cache-on-failure: true
- name: Check formatting
working-directory: apps/prime-mcp
run: cargo fmt --check
- name: Run Clippy
working-directory: apps/prime-mcp
run: cargo clippy --all-targets -- -D warnings
- name: Build release
working-directory: apps/prime-mcp
run: cargo build --release
- name: Build recall-bench
working-directory: tooling/recall-bench
run: cargo build
# ============================================================================
# Go Quality Gates (Control Plane)
# ============================================================================
go-quality:
name: Go Quality Gates
needs: [workflow-lint, workflow-validate, version-check, changes]
if: needs.changes.outputs.go == 'true' || needs.changes.outputs.workflows == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: apps/control-plane
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.26.1"
cache-dependency-path: apps/control-plane/go.sum
- name: Download dependencies
run: go mod download
- name: Verify dependencies
run: go mod verify
# Fast checks first
- name: Check formatting
run: |
if [ -n "$(gofmt -l .)" ]; then
echo "Go code is not formatted:"
gofmt -d .
exit 1
fi
# Unified linting with golangci-lint (includes staticcheck, gosec, etc.)
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v9
with:
version: v2.9.0
working-directory: apps/control-plane
args: --timeout=5m
# Then tests and build
- name: Run tests
run: go test -v -race -coverprofile=coverage.out -covermode=atomic ./...
- name: Upload coverage
uses: codecov/codecov-action@v6
if: github.event_name != 'pull_request'
with:
files: apps/control-plane/coverage.out
flags: control-plane
fail_ci_if_error: false
- name: Build binary
run: CGO_ENABLED=0 go build -ldflags="-s -w" -o control-plane .
# ============================================================================
# Elixir Quality Gates (Query Service)
# ============================================================================
elixir-query-quality:
name: Elixir Query Service Quality Gates
needs: [workflow-lint, workflow-validate, version-check, changes]
if: needs.changes.outputs.elixir == 'true' || needs.changes.outputs.workflows == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: apps/query-service
env:
MIX_ENV: test
# Disable Ryuk container for testcontainers - not needed in ephemeral CI
TESTCONTAINERS_RYUK_DISABLED: "true"
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Elixir
uses: erlef/setup-beam@v1
id: beam
with:
elixir-version: "1.18"
otp-version: "27"
# Unified cache for deps, build, and PLT with proper restore strategy
- name: Restore dependencies cache
id: deps-cache
uses: actions/cache@v5
with:
path: |
apps/query-service/deps
apps/query-service/_build
key: elixir-${{ runner.os }}-${{ steps.beam.outputs.otp-version }}-${{ steps.beam.outputs.elixir-version }}-${{ hashFiles('apps/query-service/mix.lock') }}
restore-keys: |
elixir-${{ runner.os }}-${{ steps.beam.outputs.otp-version }}-${{ steps.beam.outputs.elixir-version }}-
- name: Restore PLT cache
id: plt-cache
uses: actions/cache@v5
with:
path: apps/query-service/priv/plts
key: elixir-plt-${{ runner.os }}-${{ steps.beam.outputs.otp-version }}-${{ steps.beam.outputs.elixir-version }}-${{ hashFiles('apps/query-service/mix.lock') }}-${{ hashFiles('apps/query-service/lib/**/*.ex') }}
restore-keys: |
elixir-plt-${{ runner.os }}-${{ steps.beam.outputs.otp-version }}-${{ steps.beam.outputs.elixir-version }}-${{ hashFiles('apps/query-service/mix.lock') }}-
elixir-plt-${{ runner.os }}-${{ steps.beam.outputs.otp-version }}-${{ steps.beam.outputs.elixir-version }}-
- name: Install dependencies
run: mix deps.get
# Fast checks first
- name: Check formatting
run: mix format --check-formatted
- name: Check unused dependencies
run: mix deps.unlock --check-unused
- name: Compile with warnings as errors
run: mix compile --warnings-as-errors
- name: Run Credo (static analysis)
run: mix credo --strict
# Create PLT directory and run Dialyzer
- name: Create PLT directory
run: mkdir -p priv/plts
- name: Run Dialyzer
run: mix dialyzer --format github
# Then tests
- name: Run tests
run: mix test
# ============================================================================
# Elixir Quality Gates (MCP Server)
# ============================================================================
elixir-mcp-quality:
name: Elixir MCP Server Quality Gates
needs: [workflow-lint, workflow-validate, version-check, changes]
if: needs.changes.outputs.mcp == 'true' || needs.changes.outputs.workflows == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: apps/mcp-server-elixir
env:
MIX_ENV: test
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Elixir
uses: erlef/setup-beam@v1
id: beam
with:
elixir-version: "1.18"
otp-version: "27"
# Unified cache for deps, build, and PLT with proper restore strategy
- name: Restore dependencies cache
id: deps-cache
uses: actions/cache@v5
with:
path: |
apps/mcp-server-elixir/deps
apps/mcp-server-elixir/_build
key: elixir-mcp-${{ runner.os }}-${{ steps.beam.outputs.otp-version }}-${{ steps.beam.outputs.elixir-version }}-${{ hashFiles('apps/mcp-server-elixir/mix.lock') }}
restore-keys: |
elixir-mcp-${{ runner.os }}-${{ steps.beam.outputs.otp-version }}-${{ steps.beam.outputs.elixir-version }}-
- name: Restore PLT cache
id: plt-cache
uses: actions/cache@v5
with:
path: apps/mcp-server-elixir/priv/plts
key: elixir-mcp-plt-${{ runner.os }}-${{ steps.beam.outputs.otp-version }}-${{ steps.beam.outputs.elixir-version }}-${{ hashFiles('apps/mcp-server-elixir/mix.lock') }}-${{ hashFiles('apps/mcp-server-elixir/lib/**/*.ex') }}
restore-keys: |
elixir-mcp-plt-${{ runner.os }}-${{ steps.beam.outputs.otp-version }}-${{ steps.beam.outputs.elixir-version }}-${{ hashFiles('apps/mcp-server-elixir/mix.lock') }}-
elixir-mcp-plt-${{ runner.os }}-${{ steps.beam.outputs.otp-version }}-${{ steps.beam.outputs.elixir-version }}-
- name: Install dependencies
run: mix deps.get
# Fast checks first
- name: Check formatting
run: mix format --check-formatted
- name: Check unused dependencies
run: mix deps.unlock --check-unused
- name: Compile with warnings as errors
run: mix compile --warnings-as-errors
- name: Run Credo (static analysis)
run: mix credo --strict
# Create PLT directory and run Dialyzer
- name: Create PLT directory
run: mkdir -p priv/plts
- name: Run Dialyzer
run: mix dialyzer --format github
# Then tests
- name: Run tests
run: mix test
# ============================================================================
# Summary Gate - All Quality Checks Must Pass
# ============================================================================
quality-gate:
name: Quality Gate
runs-on: ubuntu-latest
needs:
- workflow-lint
- workflow-validate
- version-check
- changes
- rust-quality
- go-quality
- elixir-query-quality
- elixir-mcp-quality
if: always()
steps:
- name: Check quality gates
run: |
check_job() {
local name="$1"
local result="$2"
local required="$3"
if [ "$required" == "false" ]; then
echo "- ⏭️ **$name**: Skipped (no changes)"
elif [ "$result" == "success" ]; then
echo "- ✅ **$name**: Passed"
elif [ "$result" == "skipped" ]; then
echo "- ⏭️ **$name**: Skipped"
else
echo "- ❌ **$name**: Failed"
FAILED=true
fi
}
FAILED=false
{
echo "## Quality Gate Summary"
echo ""
check_job "Workflow Lint" "${{ needs.workflow-lint.result }}" "true"
check_job "Workflow Validate (act)" "${{ needs.workflow-validate.result }}" "true"
check_job "Version Check" "${{ needs.version-check.result }}" "true"
check_job "Rust Quality" "${{ needs.rust-quality.result }}" "${{ needs.changes.outputs.rust == 'true' || needs.changes.outputs.workflows == 'true' }}"
check_job "Go Quality" "${{ needs.go-quality.result }}" "${{ needs.changes.outputs.go == 'true' || needs.changes.outputs.workflows == 'true' }}"
check_job "Elixir Query" "${{ needs.elixir-query-quality.result }}" "${{ needs.changes.outputs.elixir == 'true' || needs.changes.outputs.workflows == 'true' }}"
check_job "Elixir MCP" "${{ needs.elixir-mcp-quality.result }}" "${{ needs.changes.outputs.mcp == 'true' || needs.changes.outputs.workflows == 'true' }}"
echo ""
if [ "$FAILED" == "true" ]; then
echo "### ❌ Quality Gate Failed"
else
echo "### ✅ All Quality Gates Passed"
fi
} >> "$GITHUB_STEP_SUMMARY"
if [ "$FAILED" == "true" ]; then
exit 1
fi