Merge pull request #1187 from ai-agent-assembly/v0.0.1/AAASM-3513/ref… #2603
Workflow file for this run
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 | |
| permissions: | |
| contents: read | |
| on: | |
| push: | |
| branches: [master] | |
| paths: | |
| - "aa-*/**" | |
| - "proto/**" | |
| - "conformance/**" | |
| - "Cargo.toml" | |
| - "Cargo.lock" | |
| - "deny.toml" | |
| - "openapi/**" | |
| - "schemas/**" | |
| - ".spectral.yaml" | |
| - ".github/workflows/ci.yml" | |
| - "dashboard/**" | |
| - "sonar-project.properties" | |
| - "!**/*.md" | |
| - "!**/LICENSE" | |
| - "!**/*.png" | |
| - "!**/*.jpg" | |
| - "!**/*.jpeg" | |
| - "!**/*.gif" | |
| - "!**/*.webp" | |
| - "!**/*.ico" | |
| pull_request: | |
| branches: [master] | |
| paths: | |
| - "aa-*/**" | |
| - "proto/**" | |
| - "conformance/**" | |
| - "Cargo.toml" | |
| - "Cargo.lock" | |
| - "deny.toml" | |
| - "openapi/**" | |
| - "schemas/**" | |
| - ".spectral.yaml" | |
| - ".github/workflows/ci.yml" | |
| - "dashboard/**" | |
| - "sonar-project.properties" | |
| - "!**/*.md" | |
| - "!**/LICENSE" | |
| - "!**/*.png" | |
| - "!**/*.jpg" | |
| - "!**/*.jpeg" | |
| - "!**/*.gif" | |
| - "!**/*.webp" | |
| - "!**/*.ico" | |
| # AAASM-3446: the per-PR / per-push path-filter above gates the slow eBPF | |
| # jobs (ebpf-build, e2e-ebpf-linux) to aa-ebpf*/** changes for cost control, | |
| # which means the Layer 3 (eBPF) / three-layer suite is normally SKIPPED on | |
| # master. A weekly schedule + on-demand dispatch restores standing master | |
| # coverage for the AAASM-1520/1522/1523 suites at ~1 run/week without | |
| # adding per-PR eBPF cost. The eBPF jobs' `if:` opt in to these events. | |
| schedule: | |
| - cron: "0 3 * * 1" # Mondays 03:00 UTC | |
| workflow_dispatch: | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUST_BACKTRACE: 1 | |
| # AAASM-2579: incremental artifacts bloat the rust-cache and give no | |
| # benefit on cold CI runners; pin off uniformly across every Rust job. | |
| CARGO_INCREMENTAL: 0 | |
| jobs: | |
| changes: | |
| name: Detect changed areas | |
| runs-on: ubuntu-latest | |
| outputs: | |
| rust: ${{ steps.filter.outputs.rust }} | |
| dashboard: ${{ steps.filter.outputs.dashboard }} | |
| ebpf: ${{ steps.filter.outputs.ebpf }} | |
| proto: ${{ steps.filter.outputs.proto }} | |
| schema: ${{ steps.filter.outputs.schema }} | |
| openapi: ${{ steps.filter.outputs.openapi }} | |
| storage: ${{ steps.filter.outputs.storage }} | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 | |
| id: filter | |
| with: | |
| filters: | | |
| rust: | |
| - 'aa-*/**' | |
| - 'proto/**' | |
| - 'conformance/**' | |
| - 'Cargo.toml' | |
| - 'Cargo.lock' | |
| - 'deny.toml' | |
| - 'openapi/v1.yaml' | |
| - '.spectral.yaml' | |
| - 'schemas/**' | |
| - '.github/workflows/ci.yml' | |
| dashboard: | |
| - 'dashboard/**' | |
| - 'openapi/v1.yaml' | |
| - 'sonar-project.properties' | |
| - '.github/workflows/ci.yml' | |
| # AAASM-2599: fine-grained filters so single-purpose validators run | |
| # only when their own inputs change (each is a strict subset of the | |
| # broad `rust` set above, so this can never lose coverage). Always | |
| # include this workflow so editing CI re-runs everything. | |
| proto: | |
| - 'proto/**' | |
| - 'aa-proto/**' | |
| - '.github/workflows/ci.yml' | |
| schema: | |
| - 'schemas/**' | |
| - '.github/workflows/ci.yml' | |
| openapi: | |
| - 'aa-api/**' | |
| - 'openapi/**' | |
| - '.spectral.yaml' | |
| - '.github/workflows/ci.yml' | |
| storage: | |
| - 'aa-gateway/**' | |
| - 'aa-storage*/**' | |
| - '.github/workflows/ci.yml' | |
| # AAASM-2580: gate the slow eBPF --release jobs (fat-LTO BPF build + | |
| # bpf-linker compile) so they only run when eBPF code — or this | |
| # workflow — actually changes. Non-eBPF PRs skip them. | |
| ebpf: | |
| - 'aa-ebpf*/**' | |
| - '.github/workflows/ci.yml' | |
| # AAASM-2582: build the dashboard assets (embedded by aa-cli's build.rs) | |
| # exactly once and share them via an artifact, instead of running | |
| # `pnpm install && pnpm build` in each of build/clippy/test/coverage. | |
| dashboard-assets: | |
| needs: [changes] | |
| if: needs.changes.outputs.rust == 'true' | |
| name: Build dashboard assets (shared) | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: dashboard | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 | |
| with: | |
| version: 10.9.0 | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: 20 | |
| cache: pnpm | |
| cache-dependency-path: dashboard/pnpm-lock.yaml | |
| - name: Build dashboard assets | |
| run: | | |
| pnpm install --frozen-lockfile | |
| pnpm build | |
| - name: Upload dashboard dist artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: dashboard-dist-rust | |
| path: dashboard/dist/ | |
| retention-days: 1 | |
| build: | |
| needs: [changes, dashboard-assets] | |
| if: needs.changes.outputs.rust == 'true' | |
| name: Build | |
| runs-on: ubuntu-latest | |
| env: | |
| # AAASM-2581: select mold for host-target links only. Cross targets | |
| # (bpfel/wasm32/thumbv7) keep their own linkers — this is per-target, | |
| # not a global RUSTFLAGS, on purpose. | |
| CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: "-C link-arg=-fuse-ld=mold" | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - name: Install protobuf compiler | |
| run: sudo apt-get install -y protobuf-compiler | |
| - name: Install mold linker | |
| run: sudo apt-get install -y mold | |
| - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| - name: Download prebuilt dashboard assets (required by aa-cli) | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: dashboard-dist-rust | |
| path: dashboard/dist/ | |
| - name: Build workspace | |
| # aa-ebpf requires nightly (aya-build invokes rustup run nightly). | |
| # It is validated by the dedicated ebpf-build job. | |
| run: cargo build --workspace --exclude aa-ebpf | |
| fmt: | |
| needs: [changes] | |
| if: needs.changes.outputs.rust == 'true' | |
| name: Format check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| with: | |
| components: rustfmt | |
| - name: Check formatting | |
| run: cargo fmt --all -- --check | |
| clippy: | |
| needs: [changes, dashboard-assets] | |
| if: needs.changes.outputs.rust == 'true' | |
| name: Clippy lint | |
| runs-on: ubuntu-latest | |
| env: | |
| # AAASM-2581: select mold for host-target links only. | |
| CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: "-C link-arg=-fuse-ld=mold" | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - name: Install protobuf compiler | |
| run: sudo apt-get install -y protobuf-compiler | |
| - name: Install mold linker | |
| run: sudo apt-get install -y mold | |
| - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| with: | |
| components: clippy | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| - name: Download prebuilt dashboard assets (required by aa-cli) | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: dashboard-dist-rust | |
| path: dashboard/dist/ | |
| - name: Run Clippy | |
| # aa-ebpf requires nightly (aya-build invokes rustup run nightly). | |
| # It is linted by the dedicated ebpf-build job. | |
| run: cargo clippy --workspace --all-targets --all-features --exclude aa-ebpf -- -D warnings | |
| docs: | |
| needs: [changes] | |
| if: needs.changes.outputs.rust == 'true' | |
| name: Rustdoc | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| - name: Check rustdoc (no undocumented public items) | |
| env: | |
| RUSTDOCFLAGS: "-D warnings" | |
| run: cargo doc --no-deps --all-features -p aa-core | |
| test: | |
| needs: [changes, dashboard-assets] | |
| if: needs.changes.outputs.rust == 'true' | |
| name: Test | |
| runs-on: ubuntu-latest | |
| env: | |
| # AAASM-2581: select mold for host-target links only. | |
| CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: "-C link-arg=-fuse-ld=mold" | |
| services: | |
| # AAASM-1741: postgres for the env-gated storage::postgres::tests | |
| # in aa-gateway. Tests skip cleanly when AAASM_DATABASE_URL is unset | |
| # (any non-CI environment); CI exports it on the Run tests step so | |
| # the suite actually exercises the PostgresBackend. | |
| postgres: | |
| image: postgres:18-alpine | |
| env: | |
| POSTGRES_USER: aasm | |
| POSTGRES_PASSWORD: aasm | |
| POSTGRES_DB: aasm_test | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - name: Checkout sibling node-sdk (for e2e_sdk_node fixtures) | |
| uses: actions/checkout@v7.0.0 | |
| with: | |
| repository: ai-agent-assembly/node-sdk | |
| path: node-sdk | |
| - name: Install protobuf compiler | |
| run: sudo apt-get install -y protobuf-compiler | |
| - name: Install mold linker | |
| run: sudo apt-get install -y mold | |
| - name: Free up runner disk space | |
| # AAASM-2581: selecting mold changes the rust-cache key (it hashes | |
| # RUSTFLAGS), so the first run rebuilds the workspace cold. The Test | |
| # job is the heaviest disk consumer (cold target/ + nextest archive + | |
| # node_modules + node-sdk native build + postgres testcontainer image) | |
| # and, unlike Coverage, never frees target/. Reclaim ~25 GB of | |
| # preinstalled toolchains we don't use so the cold build fits. | |
| run: | | |
| sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc \ | |
| /usr/local/.ghcup /opt/hostedtoolcache/CodeQL | |
| df -h / | |
| - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| - name: Install cargo-nextest | |
| uses: taiki-e/install-action@83c419a40a2e48673b2c523337e57f602dfe53c9 # cargo-nextest | |
| - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 | |
| with: | |
| version: 10.9.0 | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: 20 | |
| cache: pnpm | |
| cache-dependency-path: | | |
| dashboard/pnpm-lock.yaml | |
| aa-integration-tests/tests/fixtures/agents/typescript/pnpm-lock.yaml | |
| - name: Download prebuilt dashboard assets (required by aa-cli) | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: dashboard-dist-rust | |
| path: dashboard/dist/ | |
| - name: "Symlink node-sdk for TypeScript fixture file: dependency" | |
| run: ln -sfn "$GITHUB_WORKSPACE/node-sdk" "$(dirname "$GITHUB_WORKSPACE")/node-sdk" | |
| - name: Build Node.js native binding + TS dist (e2e_sdk_node real_* tests, AAASM-1602) | |
| # MUST run BEFORE the fixture's `pnpm install` — pnpm's `file:` | |
| # protocol copies the package contents at install time, so dist/ | |
| # and the napi `.node` must already exist when fixture install | |
| # runs. Use the symlinked sibling path (created above) so cargo | |
| # sees the inner Cargo.toml at its own root, not inside the | |
| # agent-assembly workspace. | |
| run: | | |
| cd "$(dirname "$GITHUB_WORKSPACE")/node-sdk" | |
| pnpm install --no-frozen-lockfile | |
| pnpm native:build | |
| pnpm build | |
| - name: Install TypeScript fixture dependencies (e2e_sdk_node) | |
| working-directory: aa-integration-tests/tests/fixtures/agents/typescript | |
| run: pnpm install --frozen-lockfile | |
| - name: Pre-build aasm binary (AAASM-2340) | |
| # Compiling aa-cli (which transitively pulls wasmtime via aa-sandbox) | |
| # once serially is cheaper than letting concurrent nextest workers | |
| # serialise on the build lock — each per-test `cargo run -p aa-cli` | |
| # re-walks the build graph and blocks on the lock under contention. | |
| # Tests then spawn the pre-built binary directly via AASM_BIN_PATH. | |
| run: cargo build --quiet -p aa-cli --bin aasm | |
| - name: Run tests | |
| # aa-ebpf requires nightly (aya-build invokes rustup run nightly) and has no | |
| # unit tests. Validated by the dedicated ebpf-build job. | |
| # Relax p99 SLA for shared CI runners; nextest runs all crate tests | |
| # concurrently which inflates tail latency well beyond bare-metal. | |
| # The dedicated Benchmark job enforces the strict 15ms default. | |
| # 5000ms absorbs the heavy noisy-neighbor variance currently seen on | |
| # GitHub-hosted runners (observed p99 1.2-2.7s under concurrent load | |
| # as of 2026-05; the prior 500ms ceiling was set when p99 was ~270ms). | |
| env: | |
| AA_BENCH_SLA_P99_MS: "5000" | |
| AAASM_DATABASE_URL: "postgres://aasm:aasm@localhost:5432/aasm_test" | |
| # AAASM-2340: skip per-test cargo-run overhead by spawning the | |
| # binary pre-built in the previous step. CliFixture::cmd and the | |
| # in-test command builders honour AASM_BIN_PATH; absent it they | |
| # fall back to cargo run for local dev. | |
| AASM_BIN_PATH: ${{ github.workspace }}/target/debug/aasm | |
| run: cargo nextest run --workspace --no-tests=pass --exclude aa-ebpf | |
| coverage: | |
| name: Coverage | |
| runs-on: ubuntu-latest | |
| needs: [changes, dashboard-assets] | |
| if: github.actor != 'dependabot[bot]' && needs.changes.outputs.rust == 'true' && (github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'run-coverage')) | |
| env: | |
| # AAASM-1741: lets the env-gated storage::postgres::tests run under | |
| # cargo llvm-cov so codecov/patch sees coverage for the PG backend. | |
| AAASM_DATABASE_URL: "postgres://aasm:aasm@localhost:5432/aasm_test" | |
| # AAASM-2581: select mold for host-target links only. | |
| CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: "-C link-arg=-fuse-ld=mold" | |
| services: | |
| postgres: | |
| image: postgres:18-alpine | |
| env: | |
| POSTGRES_USER: aasm | |
| POSTGRES_PASSWORD: aasm | |
| POSTGRES_DB: aasm_test | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| steps: | |
| - name: Free disk space (reclaim runner space for llvm-cov build) | |
| run: | | |
| sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /usr/local/share/boost /opt/hostedtoolcache/CodeQL || true | |
| sudo docker image prune --all --force || true | |
| df -h / | |
| - uses: actions/checkout@v7.0.0 | |
| - name: Checkout sibling node-sdk (for e2e_sdk_node fixtures) | |
| uses: actions/checkout@v7.0.0 | |
| with: | |
| repository: ai-agent-assembly/node-sdk | |
| path: node-sdk | |
| - name: Install protobuf compiler | |
| run: sudo apt-get install -y protobuf-compiler | |
| - name: Install mold linker | |
| run: sudo apt-get install -y mold | |
| - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| with: | |
| components: llvm-tools-preview | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| - name: Install cargo-llvm-cov | |
| uses: taiki-e/install-action@3f7f2bd74f95d407a45d96b106f26d4f6c238fab # cargo-llvm-cov | |
| - name: Install cargo-nextest | |
| uses: taiki-e/install-action@83c419a40a2e48673b2c523337e57f602dfe53c9 # cargo-nextest | |
| - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 | |
| with: | |
| version: 10.9.0 | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: 20 | |
| cache: pnpm | |
| cache-dependency-path: | | |
| dashboard/pnpm-lock.yaml | |
| aa-integration-tests/tests/fixtures/agents/typescript/pnpm-lock.yaml | |
| - name: Download prebuilt dashboard assets (required by aa-cli) | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: dashboard-dist-rust | |
| path: dashboard/dist/ | |
| - name: "Symlink node-sdk for TypeScript fixture file: dependency" | |
| run: ln -sfn "$GITHUB_WORKSPACE/node-sdk" "$(dirname "$GITHUB_WORKSPACE")/node-sdk" | |
| - name: Build Node.js native binding + TS dist (e2e_sdk_node real_* tests, AAASM-1602) | |
| # MUST run BEFORE the fixture's `pnpm install` — pnpm's `file:` | |
| # protocol copies the package contents at install time, so dist/ | |
| # and the napi `.node` must already exist when fixture install | |
| # runs. Use the symlinked sibling path (created above) so cargo | |
| # sees the inner Cargo.toml at its own root, not inside the | |
| # agent-assembly workspace. | |
| run: | | |
| cd "$(dirname "$GITHUB_WORKSPACE")/node-sdk" | |
| pnpm install --no-frozen-lockfile | |
| pnpm native:build | |
| pnpm build | |
| - name: Install TypeScript fixture dependencies (e2e_sdk_node) | |
| working-directory: aa-integration-tests/tests/fixtures/agents/typescript | |
| run: pnpm install --frozen-lockfile | |
| - name: Pre-build aasm binary (AAASM-2340) | |
| # See sonar.yml for the rationale — same pattern, same reason. | |
| run: | | |
| cargo build --quiet -p aa-cli --bin aasm | |
| mkdir -p "$RUNNER_TEMP/aasm-bin" | |
| mv target/debug/aasm "$RUNNER_TEMP/aasm-bin/aasm" | |
| rm -rf target/debug | |
| - name: Generate coverage report | |
| env: | |
| AASM_BIN_PATH: ${{ runner.temp }}/aasm-bin/aasm | |
| # aa-ebpf requires nightly and has no testable userspace logic — excluded. | |
| # aa-devtool-claude-code is run separately (step 2) without docker-integration: | |
| # the docker-integration feature builds aa-proxy from scratch inside Docker | |
| # and doesn't add instrumented coverage. Running it under --all-features would | |
| # add 8+ minutes of Docker build time with zero coverage benefit. | |
| run: | | |
| # sustained_load_p99_under_5ms, all_layers_run_independently, and | |
| # auth_rate_limit_resets_after_window are timing-sensitive tests that | |
| # fail under llvm-cov instrumentation overhead — skipped in coverage | |
| # runs only. The `handle_shutdown_*` local_mode tests (AAASM-1728) join | |
| # the same allow-list: they spawn an axum server + reqwest client and | |
| # assert sub-500 ms shutdown, which wedges under llvm-cov's 2-5× overhead. | |
| # | |
| # The `ebpf_*` tests in aa-integration-tests/tests/e2e_ebpf.rs are the | |
| # AAASM-1520 Layer 3 suite: they require nightly + bpf-linker + the | |
| # built BPF probes + root, none of which Coverage provides | |
| # (`--all-features` activates them but `--exclude aa-ebpf` means the | |
| # BPF object never gets built, so `Ebpf::load` trips on empty bytes). | |
| # The dedicated `e2e-ebpf-linux` job is authoritative for these. | |
| cargo llvm-cov nextest --no-report --all-features --workspace \ | |
| --exclude aa-ebpf --exclude aa-devtool-claude-code \ | |
| --no-tests=pass \ | |
| -E 'not test(sustained_load_p99_under_5ms) and not test(all_layers_run_independently) and not test(auth_rate_limit_resets_after_window) and not test(ebpf_) and not test(handle_shutdown_)' | |
| cargo llvm-cov nextest --no-report -p aa-devtool-claude-code --no-tests=pass | |
| cargo llvm-cov --no-run --codecov --output-path codecov.xml | |
| cargo llvm-cov --no-run --lcov --output-path lcov.info | |
| - name: Upload to Codecov | |
| uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 | |
| with: | |
| files: ./codecov.xml | |
| name: Rust unit tests | |
| slug: ai-agent-assembly/agent-assembly | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| flags: unit | |
| verbose: true | |
| fail_ci_if_error: false | |
| - name: Upload Rust coverage (LCOV) for SonarCloud | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: rust-coverage-lcov | |
| path: lcov.info | |
| retention-days: 1 | |
| # AAASM-1858: dedicated TimescaleDB job for env-gated tests in | |
| # aa-gateway. Sibling tests (in storage::timescale::tests and | |
| # storage::postgres::tests) check the `TIMESCALEDB_AVAILABLE` env | |
| # var and either run-and-assert against a TimescaleDB-enabled | |
| # cluster (this job) or skip (the regular `test` job above, which | |
| # runs against vanilla postgres:18-alpine and leaves the env unset). | |
| # The `timescale/timescaledb` image lags vanilla postgres by one | |
| # major; pg17 is the latest published tag as of 2026-05. | |
| timescaledb-tests: | |
| needs: [changes] | |
| if: needs.changes.outputs.storage == 'true' | |
| name: TimescaleDB Tests | |
| runs-on: ubuntu-latest | |
| services: | |
| timescaledb: | |
| image: timescale/timescaledb:latest-pg17 | |
| env: | |
| POSTGRES_USER: aasm | |
| POSTGRES_PASSWORD: aasm | |
| POSTGRES_DB: aasm_test | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - name: Install protobuf compiler | |
| run: sudo apt-get install -y protobuf-compiler | |
| - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| - name: Install cargo-nextest | |
| uses: taiki-e/install-action@83c419a40a2e48673b2c523337e57f602dfe53c9 # cargo-nextest | |
| - name: Run TimescaleDB tests | |
| # The `timescale` substring filter matches all 9 env-gated tests | |
| # across SD-1..SD-4: storage::timescale::tests::* (probe) plus | |
| # storage::postgres::tests::{migrate_0002_*timescaledb*, | |
| # apply_timescaledb_setup_*, healthcheck_reports_timescale_*}. | |
| env: | |
| AAASM_DATABASE_URL: "postgres://aasm:aasm@localhost:5432/aasm_test" | |
| TIMESCALEDB_AVAILABLE: "1" | |
| run: cargo nextest run -p aa-gateway timescale | |
| migration-drift-check: | |
| needs: [changes] | |
| if: needs.changes.outputs.storage == 'true' | |
| name: Migration drift check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - name: Install protobuf compiler | |
| run: sudo apt-get install -y protobuf-compiler | |
| - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| - name: Install cargo-nextest | |
| uses: taiki-e/install-action@83c419a40a2e48673b2c523337e57f602dfe53c9 # cargo-nextest | |
| - name: Run gateway migration drift test | |
| # Boots a throwaway Postgres (testcontainers) and applies the | |
| # aa-storage-postgres MIGRATOR through the gateway. Fails if the | |
| # canonical MVP migrations no longer apply cleanly or audit_logs drifts. | |
| run: cargo nextest run -p aa-gateway -E 'binary(migration_boot)' | |
| deny: | |
| needs: [changes] | |
| if: needs.changes.outputs.rust == 'true' | |
| name: Dependency checks | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| - uses: EmbarkStudios/cargo-deny-action@bb137d7af7e4fb67e5f82a49c4fce4fad40782fe # v2 | |
| with: | |
| command: check | |
| arguments: --all-features | |
| no-std: | |
| needs: [changes] | |
| if: needs.changes.outputs.rust == 'true' | |
| name: no_std targets | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| include: | |
| - target: wasm32-unknown-unknown | |
| - target: thumbv7em-none-eabihf | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| - name: Build aa-core (no_std + alloc) | |
| env: | |
| TARGET: ${{ matrix.target }} | |
| run: | | |
| cargo build -p aa-core \ | |
| --target "$TARGET" \ | |
| --no-default-features \ | |
| --features alloc | |
| buf-lint: | |
| needs: [changes] | |
| if: needs.changes.outputs.proto == 'true' | |
| name: Proto lint & breaking check (buf) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| with: | |
| fetch-depth: 0 # full history needed for buf breaking --against | |
| - uses: bufbuild/buf-setup-action@v1.50.0 | |
| with: | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Lint proto files | |
| run: buf lint | |
| working-directory: proto | |
| - name: Create local base branch for buf breaking check | |
| if: github.event_name == 'pull_request' | |
| run: git fetch origin ${{ github.base_ref }}:${{ github.base_ref }} | |
| - name: Check breaking changes against base branch | |
| if: github.event_name == 'pull_request' | |
| run: | | |
| buf breaking \ | |
| --against "../.git#branch=${{ github.base_ref }},subdir=proto" | |
| working-directory: proto | |
| schema-lint: | |
| needs: [changes] | |
| if: needs.changes.outputs.schema == 'true' | |
| name: Schema lint & validation (ajv) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: '20' | |
| - name: Install ajv-cli | |
| run: npm install -g ajv-cli | |
| - name: Validate schema is self-consistent (ajv --strict) | |
| run: ajv compile --strict -s schemas/policy/v1/policy-document.schema.json | |
| - name: Validate example policy documents against schema | |
| run: | | |
| ajv validate \ | |
| -s schemas/policy/v1/policy-document.schema.json \ | |
| -d schemas/examples/strict.yaml | |
| ajv validate \ | |
| -s schemas/policy/v1/policy-document.schema.json \ | |
| -d schemas/examples/balanced.yaml | |
| ajv validate \ | |
| -s schemas/policy/v1/policy-document.schema.json \ | |
| -d schemas/examples/audit-only.yaml | |
| openapi-drift: | |
| needs: [changes] | |
| if: needs.changes.outputs.openapi == 'true' | |
| name: OpenAPI spec drift check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - name: Install protobuf compiler | |
| run: sudo apt-get install -y protobuf-compiler | |
| - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| - name: Generate OpenAPI spec from code | |
| run: cargo run -p aa-api --bin generate_openapi > /tmp/generated-v1.yaml | |
| - name: Check spec is up to date | |
| run: diff openapi/v1.yaml /tmp/generated-v1.yaml | |
| openapi-lint: | |
| needs: [changes] | |
| if: needs.changes.outputs.openapi == 'true' | |
| name: OpenAPI spec lint (Spectral) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: '20' | |
| - name: Install Spectral CLI | |
| run: npm install -g @stoplight/spectral-cli | |
| - name: Lint OpenAPI spec | |
| run: spectral lint openapi/v1.yaml --fail-severity warn | |
| compat-matrix-check: | |
| name: Compatibility matrix update check | |
| runs-on: ubuntu-latest | |
| needs: [changes] | |
| if: github.event_name == 'pull_request' && needs.changes.outputs.rust == 'true' | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check compatibility matrix is updated when versions change | |
| run: bash .ci/check-compatibility-matrix.sh | |
| ebpf-build: | |
| needs: [changes] | |
| # AAASM-2580: per-PR/push, only run when aa-ebpf* (or this workflow) changed. | |
| # AAASM-3446: also run on the weekly schedule and on manual dispatch so the | |
| # Layer 3 suite keeps standing master coverage despite the path-filter. | |
| if: needs.changes.outputs.ebpf == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' | |
| name: eBPF probes build (Linux nightly) | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: aa-ebpf-probes | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - name: Install protobuf compiler | |
| # Required by aa-proto (transitive dep of aa-runtime) for the | |
| # multi-layer integration test step. | |
| run: sudo apt-get install -y protobuf-compiler | |
| - uses: dtolnay/rust-toolchain@nightly | |
| with: | |
| # Do NOT add bpfel-unknown-none to targets: there is no precompiled rust-std | |
| # for this target. BPF programs are built from source via -Zbuild-std=core | |
| # (configured in aa-ebpf-probes/.cargo/config.toml). Only rust-src is needed. | |
| # clippy is required for the "Clippy on eBPF probes" step below. | |
| components: rust-src,clippy | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| with: | |
| workspaces: aa-ebpf-probes | |
| - name: Cache bpf-linker binary | |
| # Cache the compiled bpf-linker binary so subsequent CI runs skip the | |
| # slow bundled-LLVM compile (~5-10 min). Key includes the version so a | |
| # version bump automatically invalidates the cache. | |
| uses: actions/cache@v5 | |
| id: cache-bpf-linker | |
| with: | |
| path: ~/.cargo/bin/bpf-linker | |
| key: bpf-linker-${{ runner.os }}-0.10.3 | |
| - name: Install bpf-linker | |
| # bpf-linker is the custom linker required for the bpfel-unknown-none target. | |
| # Using the bundled-LLVM default (no --no-default-features) avoids fragile | |
| # system-LLVM package discovery. Only runs on cache miss. | |
| if: steps.cache-bpf-linker.outputs.cache-hit != 'true' | |
| run: cargo install bpf-linker | |
| - name: Build eBPF probes | |
| run: cargo build --release | |
| - name: Clippy on eBPF probes | |
| # --all-targets would try to build test harness binaries, which require the | |
| # `test` crate — unavailable on the no_std bpfel-unknown-none target. | |
| # --bins lints only the BPF program binaries. | |
| run: cargo clippy --bins -- -D warnings | |
| - name: Integration test — load and attach aa-file-io kprobe | |
| # Runs from the workspace root so cargo can resolve the aa-ebpf package. | |
| # BPF loading requires CAP_BPF + CAP_PERFMON (root). sudo -E preserves | |
| # CARGO_HOME, RUSTUP_HOME, and PATH so cargo uses the already-installed | |
| # nightly toolchain and bpf-linker. | |
| working-directory: ${{ github.workspace }} | |
| # sudo runs as root (/root/.rustup, /root/.cargo) but the nightly toolchain | |
| # was installed for the runner user. RUSTUP_HOME and CARGO_HOME are not set | |
| # as env vars on GitHub runners — they default to ~/.rustup/~/.cargo, which | |
| # expands to /root/... under sudo. Capture them from the runner's $HOME first, | |
| # then pass explicit paths so root reuses the already-installed toolchain. | |
| run: | | |
| RUNNER_RUSTUP="${HOME}/.rustup" | |
| RUNNER_CARGO="${HOME}/.cargo" | |
| sudo env \ | |
| "PATH=$PATH" \ | |
| "RUSTUP_HOME=${RUNNER_RUSTUP}" \ | |
| "CARGO_HOME=${RUNNER_CARGO}" \ | |
| cargo +nightly test -p aa-ebpf --features integration-test --test load_hello -- --nocapture | |
| - name: Integration test — multi-layer runtime (proxy + eBPF) | |
| # Exercises both proxy and eBPF layers on a shared broadcast channel. | |
| # The test uses the integration-test feature gate and requires root | |
| # for eBPF loader attachment. Tests that require root but don't have | |
| # it will gracefully skip with a log message. | |
| working-directory: ${{ github.workspace }} | |
| run: | | |
| RUNNER_RUSTUP="${HOME}/.rustup" | |
| RUNNER_CARGO="${HOME}/.cargo" | |
| sudo env \ | |
| "PATH=$PATH" \ | |
| "RUSTUP_HOME=${RUNNER_RUSTUP}" \ | |
| "CARGO_HOME=${RUNNER_CARGO}" \ | |
| cargo test -p aa-runtime --features integration-test -- layer_integration --nocapture --test-threads=1 | |
| e2e-ebpf-linux: | |
| needs: [changes] | |
| # AAASM-2580: per-PR/push, only run when aa-ebpf* (or this workflow) changed. | |
| # AAASM-3446: also run on the weekly schedule and on manual dispatch so the | |
| # Layer 3 e2e suite keeps standing master coverage despite the path-filter. | |
| if: needs.changes.outputs.ebpf == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' | |
| # AAASM-1520 / F116 ST-H — Layer 3 (eBPF) end-to-end interception suite. | |
| # Linux-only, runs as root for CAP_BPF + CAP_PERFMON; the test source is | |
| # `#[cfg(all(target_os = "linux", feature = "integration-test"))]` so it | |
| # is invisible to every other job, including the macOS build job. | |
| # | |
| # Kernel requirement: >= 5.10 with BTF (CONFIG_DEBUG_INFO_BTF=y). The | |
| # current GitHub `ubuntu-latest` images ship a 6.x kernel with BTF | |
| # enabled. On older kernels the BPF verifier rejects the probe load and | |
| # the tests fail loudly at startup (which is the intended degradation | |
| # mode — there is no value pretending to test Layer 3 on a kernel that | |
| # cannot host the probes). | |
| name: e2e — Layer 3 eBPF (Linux) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - name: Install protobuf compiler | |
| # Required by aa-proto (transitive dep of aa-runtime/aa-gateway). | |
| run: sudo apt-get install -y protobuf-compiler | |
| - name: Install curl | |
| # curl is the libssl-backed agent for tests 1, 3, 4 — its `SSL_write` | |
| # call is what fires the TLS uprobe. ubuntu-latest already ships curl; | |
| # the explicit install guards against future image variants that strip it. | |
| # | |
| # bpftool is intentionally *not* installed: on ubuntu-latest it is a | |
| # virtual package satisfied only by `linux-tools-<kernel-version>`, | |
| # which is fragile to runner-image kernel drift. Test 6 invokes | |
| # bpftool via `if let Ok(out) = Command::new("bpftool")...` — its | |
| # absence is a no-op, and the load-twice invariant in the same test | |
| # is the real "no kernel-resource leak" assertion. | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y curl | |
| - uses: dtolnay/rust-toolchain@nightly | |
| with: | |
| # Same justification as the `ebpf-build` job: bpfel-unknown-none has | |
| # no precompiled rust-std, so the BPF probes build via | |
| # `-Zbuild-std=core` which needs rust-src on nightly. | |
| components: rust-src,clippy | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| - name: Cache bpf-linker binary | |
| uses: actions/cache@v5 | |
| id: cache-bpf-linker | |
| with: | |
| path: ~/.cargo/bin/bpf-linker | |
| key: bpf-linker-${{ runner.os }}-0.10.3 | |
| - name: Install bpf-linker | |
| if: steps.cache-bpf-linker.outputs.cache-hit != 'true' | |
| run: cargo install bpf-linker | |
| - name: Build BPF probes | |
| # Pre-builds the BPF ELF objects that `aa-ebpf`'s build.rs embeds | |
| # via `include_bytes_aligned!`. Doing it explicitly here surfaces | |
| # any verifier or linker failure separately from the test run. | |
| working-directory: aa-ebpf-probes | |
| run: cargo build --release | |
| - name: Run AAASM-1520 e2e suite | |
| # Loading BPF programs requires CAP_BPF + CAP_PERFMON (root). We | |
| # preserve the runner-user's RUSTUP_HOME / CARGO_HOME under sudo so | |
| # the already-installed nightly toolchain and bpf-linker are reused | |
| # rather than re-downloaded. `--test-threads=1` is required because | |
| # tests 1/3/4 all attach system-wide TLS uprobes and would interfere | |
| # with each other if run in parallel; test 2 modifies a shared BPF | |
| # map. | |
| run: | | |
| RUNNER_RUSTUP="${HOME}/.rustup" | |
| RUNNER_CARGO="${HOME}/.cargo" | |
| sudo env \ | |
| "PATH=$PATH" \ | |
| "RUSTUP_HOME=${RUNNER_RUSTUP}" \ | |
| "CARGO_HOME=${RUNNER_CARGO}" \ | |
| cargo +nightly test \ | |
| -p aa-integration-tests \ | |
| --features integration-test \ | |
| --test e2e_ebpf \ | |
| -- --nocapture --test-threads=1 | |
| - name: Run AAASM-1522 e2e file monitoring suite | |
| # F116 ST-J — same root-required, single-threaded execution profile | |
| # as ST-H. The tests share the file-IO PID_FILTER BPF map and rely | |
| # on per-test PID registration not racing with other suites, so they | |
| # run in their own `cargo +nightly test` invocation (separate test | |
| # binary) with `--test-threads=1`. Kept as a distinct step so a ST-J | |
| # failure does not mask a ST-H failure (or vice versa) in CI output. | |
| run: | | |
| RUNNER_RUSTUP="${HOME}/.rustup" | |
| RUNNER_CARGO="${HOME}/.cargo" | |
| sudo env \ | |
| "PATH=$PATH" \ | |
| "RUSTUP_HOME=${RUNNER_RUSTUP}" \ | |
| "CARGO_HOME=${RUNNER_CARGO}" \ | |
| cargo +nightly test \ | |
| -p aa-integration-tests \ | |
| --features integration-test \ | |
| --test e2e_file_monitoring \ | |
| -- --nocapture --test-threads=1 | |
| - name: Run AAASM-1523 e2e three-layer suite | |
| # AAASM-1523 / F116 ST-K — three-layer unified audit-stream tests. | |
| # Drives the in-process `aa_gateway::AuditWriter` mpsc pipeline | |
| # plus the `three_layers_driver.py` fixture for on-host realism. | |
| # No BPF load is required by the tests themselves — see the | |
| # file-level divergence note in | |
| # `aa-integration-tests/tests/e2e_three_layers_together.rs` for | |
| # why. Runs in the same Linux CI lane as ST-H per the AC | |
| # ("shared with ST-H + ST-J") so the protoc + nightly toolchain | |
| # are reused. | |
| # | |
| # `sudo env ...` is required only because the AAASM-1520 step | |
| # ran as root and the cargo `target/` directory it produced is | |
| # now owned by root; running this step as the unprivileged | |
| # runner user would trip `target/debug/.cargo-build-lock` | |
| # permission-denied. We preserve RUSTUP_HOME / CARGO_HOME so | |
| # the nightly toolchain is the runner-user one (same trick as | |
| # the ST-H step). | |
| run: | | |
| RUNNER_RUSTUP="${HOME}/.rustup" | |
| RUNNER_CARGO="${HOME}/.cargo" | |
| sudo env \ | |
| "PATH=$PATH" \ | |
| "RUSTUP_HOME=${RUNNER_RUSTUP}" \ | |
| "CARGO_HOME=${RUNNER_CARGO}" \ | |
| cargo +nightly test \ | |
| -p aa-integration-tests \ | |
| --features integration-test \ | |
| --test e2e_three_layers_together \ | |
| -- --nocapture --test-threads=1 | |
| benchmark: | |
| name: Benchmark | |
| runs-on: ubuntu-latest | |
| needs: [changes, build] | |
| if: needs.changes.outputs.rust == 'true' && (github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'run-benchmark')) | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - name: Install protobuf compiler | |
| run: sudo apt-get install -y protobuf-compiler | |
| - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| - name: Run criterion benchmarks | |
| run: | | |
| cargo bench -p aa-security --bench scanner_bench | |
| cargo bench -p aa-gateway --bench policy_check | |
| cargo bench -p aa-proxy --bench intercept_bench | |
| - name: Run latency SLA test (5s burst) | |
| # AAASM-1452: shared GitHub-hosted runners exhibit noisy-neighbour | |
| # tail latency that breaches the test's bare-metal 15ms default | |
| # (observed p99 spikes to 733ms+ even on the dedicated Benchmark | |
| # job under steal-time contention). 1500ms absorbs the runner | |
| # variance while still surfacing any real ≥100× regression. The | |
| # 5ms bare-metal target stays enforced by the test name; only the | |
| # SLA assertion is relaxed. | |
| env: | |
| AA_BENCH_SLA_P99_MS: "1500" | |
| run: cargo test -p aa-gateway --test policy_latency_test -- --nocapture | |
| conformance: | |
| needs: [changes] | |
| if: needs.changes.outputs.rust == 'true' | |
| name: Rust conformance suite | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - name: Install protobuf compiler | |
| run: sudo apt-get install -y protobuf-compiler | |
| - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| - name: Run conformance suite (all categories) | |
| run: cargo test -p conformance | |
| # --------------------------------------------------------------------------- | |
| # SDK conformance placeholders — remove continue-on-error when SDK is ready | |
| # --------------------------------------------------------------------------- | |
| conformance-python: | |
| needs: [changes] | |
| if: needs.changes.outputs.rust == 'true' | |
| name: Python SDK conformance (placeholder) | |
| runs-on: ubuntu-latest | |
| continue-on-error: true | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Install runner dependencies | |
| run: pip install -r conformance/runner/requirements.txt | |
| - name: Run Python conformance runner | |
| run: python conformance/runner/runner.py | |
| # AA_SDK_MODULE is unset — runner uses no-op stub and exits 1 until a | |
| # real SDK implementation is wired up. Set AA_SDK_MODULE in a secrets or | |
| # env block when the Python SDK ships. | |
| conformance-node: | |
| needs: [changes] | |
| if: needs.changes.outputs.rust == 'true' | |
| name: Node.js SDK conformance (placeholder) | |
| runs-on: ubuntu-latest | |
| continue-on-error: true | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: "20" | |
| - name: Node.js SDK conformance (not yet implemented) | |
| run: | | |
| echo "Node.js SDK conformance runner not yet implemented." | |
| echo "Add a conformance/runner/runner.js and wire it up here." | |
| exit 0 | |
| conformance-go: | |
| needs: [changes] | |
| if: needs.changes.outputs.rust == 'true' | |
| name: Go SDK conformance (placeholder) | |
| runs-on: ubuntu-latest | |
| continue-on-error: true | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version: "1.22" | |
| - name: Go SDK conformance (not yet implemented) | |
| run: | | |
| echo "Go SDK conformance runner not yet implemented." | |
| echo "Add a conformance/runner/runner.go and wire it up here." | |
| exit 0 | |
| # ---- Dashboard (TypeScript) jobs, gated on dashboard changes ---------------- | |
| dashboard-typecheck: | |
| name: Dashboard (type-check + lint) | |
| needs: [changes] | |
| if: needs.changes.outputs.dashboard == 'true' | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: dashboard | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 | |
| with: | |
| version: 10.9.0 | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: 20 | |
| cache: pnpm | |
| cache-dependency-path: dashboard/pnpm-lock.yaml | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Generate API types from OpenAPI spec | |
| run: pnpm generate:api | |
| - name: Type check | |
| run: pnpm type-check | |
| - name: Lint | |
| run: pnpm lint | |
| dashboard-build: | |
| name: Dashboard build | |
| needs: [changes, dashboard-typecheck] | |
| if: needs.changes.outputs.dashboard == 'true' | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: dashboard | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 | |
| with: | |
| version: 10.9.0 | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: 20 | |
| cache: pnpm | |
| cache-dependency-path: dashboard/pnpm-lock.yaml | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Generate API types from OpenAPI spec | |
| run: pnpm generate:api | |
| - name: Build production bundle | |
| run: pnpm build | |
| - name: Upload dist artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: dashboard-dist | |
| path: dashboard/dist/ | |
| retention-days: 1 | |
| dashboard-test: | |
| name: Dashboard tests + coverage | |
| needs: [changes, dashboard-typecheck] | |
| if: needs.changes.outputs.dashboard == 'true' | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: dashboard | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 | |
| with: | |
| version: 10.9.0 | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: 20 | |
| cache: pnpm | |
| cache-dependency-path: dashboard/pnpm-lock.yaml | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Generate API types from OpenAPI spec | |
| run: pnpm generate:api | |
| - name: Run tests with coverage | |
| run: pnpm test:coverage | |
| - name: Upload TypeScript coverage (LCOV) for SonarCloud | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: dashboard-coverage-lcov | |
| path: dashboard/coverage/lcov.info | |
| retention-days: 1 | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 | |
| with: | |
| files: dashboard/coverage/lcov.info | |
| name: Dashboard (TypeScript) | |
| flags: dashboard | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| dashboard-codegen-drift: | |
| name: Dashboard codegen drift check | |
| needs: [changes] | |
| if: needs.changes.outputs.dashboard == 'true' | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: dashboard | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 | |
| with: | |
| version: 10.9.0 | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: 20 | |
| cache: pnpm | |
| cache-dependency-path: dashboard/pnpm-lock.yaml | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Regenerate API types | |
| run: pnpm generate:api | |
| - name: Check generated types are up to date | |
| run: git diff --exit-code src/api/generated/ | |
| aa-cli-compat: | |
| name: aa-cli build compat (dashboard embed) | |
| needs: [changes, dashboard-build] | |
| if: needs.changes.outputs.dashboard == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - name: Install protobuf compiler | |
| run: sudo apt-get install -y protobuf-compiler | |
| - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| - name: Download dashboard dist artifact | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: dashboard-dist | |
| path: dashboard/dist/ | |
| - name: Cache Cargo registry | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.cargo/registry | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }} | |
| - name: Verify aa-cli compiles with embedded assets | |
| run: cargo build -p aa-cli | |
| # SonarCloud analysis. Reuses the Rust LCOV from the `coverage` job and the | |
| # TypeScript LCOV from the `dashboard-test` job (both downloaded as artifacts) | |
| # instead of re-running any tests. `needs: [changes, coverage, dashboard-test]` | |
| # — either coverage producer may be skipped (path-gated), so the Rust steps run | |
| # only when `coverage` succeeded and the TS download only when `dashboard-test` | |
| # succeeded. Runs in the pull_request context so PR decoration works (AAASM-2542). | |
| # | |
| # AAASM-3197: SonarCloud treats every master-branch analysis as a FULL snapshot | |
| # that REPLACES the project's coverage. A push that touches only one side (e.g. | |
| # dashboard-only → `coverage` skipped) would otherwise publish a partial report | |
| # that wipes the other side's coverage to 0. On pushes, whichever producer was | |
| # skipped/failed is backfilled from the last successful master run so the | |
| # snapshot is always complete. PR analysis (new-code-only) is left unchanged. | |
| sonar: | |
| name: SonarCloud analysis | |
| needs: [changes, coverage, dashboard-test] | |
| if: ${{ !cancelled() && needs.changes.result == 'success' && github.actor != 'dependabot[bot]' && (github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'run-coverage') || needs.changes.outputs.dashboard == 'true') }} | |
| runs-on: ubuntu-latest | |
| # AAASM-3197: actions:read lets `gh run download` pull the last successful | |
| # master LCOV when a coverage producer was skipped this run (see fallback | |
| # steps below). contents:read mirrors the workflow default for checkout. | |
| permissions: | |
| contents: read | |
| actions: read | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| with: | |
| fetch-depth: 0 | |
| # ---- Rust coverage + clippy (only when the Rust side ran) -------------- | |
| - name: Install protobuf compiler | |
| if: needs.coverage.result == 'success' | |
| run: sudo apt-get install -y protobuf-compiler | |
| - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| if: needs.coverage.result == 'success' | |
| with: | |
| components: clippy | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| if: needs.coverage.result == 'success' | |
| - name: Generate Clippy report | |
| if: needs.coverage.result == 'success' | |
| run: cargo clippy --all-targets --all-features --exclude aa-ebpf --message-format=json 2>&1 | tee clippy-report.json || true | |
| - name: Download Rust coverage (LCOV) from the Coverage job | |
| if: needs.coverage.result == 'success' | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: rust-coverage-lcov | |
| path: . | |
| # AAASM-3197: master-branch analysis is a FULL snapshot — a partial report | |
| # wipes the other side's coverage to 0. When the `coverage` job was skipped | |
| # or failed on a push (non-Rust change), backfill `lcov.info` from the most | |
| # recent successful master CI run so the snapshot stays complete. PR runs are | |
| # new-code-decorated and intentionally left untouched. | |
| - name: Backfill Rust coverage (LCOV) from last successful master run | |
| if: ${{ github.event_name == 'push' && needs.coverage.result != 'success' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| run_id="$(gh run list --workflow ci.yml --branch master \ | |
| --status success --limit 1 --json databaseId \ | |
| --jq '.[0].databaseId')" | |
| if [ -z "$run_id" ]; then | |
| echo "::warning::No prior successful master CI run found; scanning without backfilled Rust LCOV." | |
| exit 0 | |
| fi | |
| echo "Backfilling rust-coverage-lcov from master run ${run_id}." | |
| gh run download "$run_id" --name rust-coverage-lcov --dir . \ | |
| || echo "::warning::rust-coverage-lcov artifact unavailable on run ${run_id} (expired/absent); scanning without it." | |
| # ---- TypeScript coverage (only when the dashboard side ran) ------------ | |
| - name: Download TypeScript coverage (LCOV) from the Dashboard test job | |
| if: needs.dashboard-test.result == 'success' | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: dashboard-coverage-lcov | |
| path: dashboard/coverage | |
| # AAASM-3197: symmetric backfill for the TypeScript side. When | |
| # `dashboard-test` was skipped or failed on a push (Rust-only change), | |
| # pull `dashboard/coverage/lcov.info` from the last successful master run | |
| # so the full-snapshot scan does not wipe dashboard coverage to 0. | |
| - name: Backfill TypeScript coverage (LCOV) from last successful master run | |
| if: ${{ github.event_name == 'push' && needs.dashboard-test.result != 'success' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| run_id="$(gh run list --workflow ci.yml --branch master \ | |
| --status success --limit 1 --json databaseId \ | |
| --jq '.[0].databaseId')" | |
| if [ -z "$run_id" ]; then | |
| echo "::warning::No prior successful master CI run found; scanning without backfilled TypeScript LCOV." | |
| exit 0 | |
| fi | |
| echo "Backfilling dashboard-coverage-lcov from master run ${run_id}." | |
| gh run download "$run_id" --name dashboard-coverage-lcov --dir dashboard/coverage \ | |
| || echo "::warning::dashboard-coverage-lcov artifact unavailable on run ${run_id} (expired/absent); scanning without it." | |
| # ---- Scan ------------------------------------------------------------- | |
| - name: Cache SonarCloud analysis state | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.sonar/cache | |
| key: ${{ runner.os }}-sonar-${{ hashFiles('sonar-project.properties') }} | |
| restore-keys: | | |
| ${{ runner.os }}-sonar- | |
| - name: SonarCloud scan (attempt 1) | |
| id: sonar-scan-attempt-1 | |
| continue-on-error: true | |
| uses: SonarSource/sonarqube-scan-action@713881670b6b3676cda39549040e2d88c70d582e # v8.2.0 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| - name: Wait before retry (attempt 2) | |
| if: steps.sonar-scan-attempt-1.outcome == 'failure' | |
| run: | | |
| echo "::warning::SonarCloud scanner failed on attempt 1 (likely transient CDN flake); waiting 30s before retry." | |
| sleep 30 | |
| - name: SonarCloud scan (attempt 2) | |
| id: sonar-scan-attempt-2 | |
| if: steps.sonar-scan-attempt-1.outcome == 'failure' | |
| continue-on-error: true | |
| uses: SonarSource/sonarqube-scan-action@713881670b6b3676cda39549040e2d88c70d582e # v8.2.0 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| - name: Wait before retry (attempt 3) | |
| if: steps.sonar-scan-attempt-1.outcome == 'failure' && steps.sonar-scan-attempt-2.outcome == 'failure' | |
| run: | | |
| echo "::warning::SonarCloud scanner failed twice; waiting 30s before final retry." | |
| sleep 30 | |
| - name: SonarCloud scan (attempt 3 — final) | |
| if: steps.sonar-scan-attempt-1.outcome == 'failure' && steps.sonar-scan-attempt-2.outcome == 'failure' | |
| uses: SonarSource/sonarqube-scan-action@713881670b6b3676cda39549040e2d88c70d582e # v8.2.0 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| # AAASM-2599: single aggregate gate that succeeds only when every functional | |
| # CI job passed or was intentionally skipped (path-routed). This is the one | |
| # status to make a required check in branch protection — it stays green for | |
| # PRs that legitimately skip whole areas, yet fails if any job failed or was | |
| # cancelled. Quality/acceptance jobs (coverage, sonar) are deliberately NOT | |
| # gated here: their failures are advisory and must not block merge. | |
| ci-success: | |
| name: CI Success | |
| runs-on: ubuntu-latest | |
| if: always() | |
| needs: | |
| - changes | |
| - dashboard-assets | |
| - build | |
| - fmt | |
| - clippy | |
| - docs | |
| - test | |
| - timescaledb-tests | |
| - migration-drift-check | |
| - deny | |
| - no-std | |
| - buf-lint | |
| - schema-lint | |
| - openapi-drift | |
| - openapi-lint | |
| - compat-matrix-check | |
| - ebpf-build | |
| - e2e-ebpf-linux | |
| - benchmark | |
| - conformance | |
| - conformance-python | |
| - conformance-node | |
| - conformance-go | |
| - dashboard-typecheck | |
| - dashboard-build | |
| - dashboard-test | |
| - dashboard-codegen-drift | |
| - aa-cli-compat | |
| steps: | |
| - name: Verify no required job failed or was cancelled | |
| run: | | |
| results="${{ join(needs.*.result, ' ') }}" | |
| echo "Job results: $results" | |
| for r in $results; do | |
| case "$r" in | |
| failure|cancelled) | |
| echo "::error::A required CI job concluded with '$r'" | |
| exit 1 | |
| ;; | |
| esac | |
| done | |
| echo "All required CI jobs passed or were intentionally skipped." | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} |