Skip to content

Merge pull request #1187 from ai-agent-assembly/v0.0.1/AAASM-3513/ref… #2603

Merge pull request #1187 from ai-agent-assembly/v0.0.1/AAASM-3513/ref…

Merge pull request #1187 from ai-agent-assembly/v0.0.1/AAASM-3513/ref… #2603

Workflow file for this run

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' }}