control-plane: add /api/v1/agent-echo reference x402 endpoint #589
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |