Repository navigation
feat(inspect-ai): add OCI container execution mode and Compose parser #1412
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| pull_request: | |
| push: | |
| branches: [main] | |
| # A new PR push supersedes the older run for that PR. Main runs are never | |
| # cancelled: each merged commit keeps its own post-merge signal. | |
| concurrency: | |
| group: ci-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| jobs: | |
| scope: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| owners: ${{ steps.scope.outputs.owners }} | |
| steps: | |
| - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd | |
| with: | |
| fetch-depth: 0 | |
| - name: Classify changed path owners | |
| id: scope | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| PUSH_BASE_SHA: ${{ github.event.before }} | |
| run: | | |
| set -euo pipefail | |
| base_sha="$PUSH_BASE_SHA" | |
| if [ "$EVENT_NAME" = pull_request ]; then | |
| base_sha="$PR_BASE_SHA" | |
| fi | |
| if [ -n "$base_sha" ] && git cat-file -e "$base_sha^{commit}" 2>/dev/null; then | |
| owners=$(git diff --name-only -z "$base_sha"...HEAD | \ | |
| python3 build_system/scripts/ci/classify-ci-scope.py --owners) | |
| else | |
| owners=$(printf '%s\0' '.github/workflows/ci.yaml' | \ | |
| python3 build_system/scripts/ci/classify-ci-scope.py --owners) | |
| fi | |
| echo "owners=$owners" >> "$GITHUB_OUTPUT" | |
| fast-gate: | |
| uses: ./.github/workflows/fast-gate.yaml | |
| # --------------------------------------------------------------------------- | |
| # Linux: compile + test KVM hypervisor backend (cfg(target_os = "linux")) | |
| # --------------------------------------------------------------------------- | |
| test-linux: | |
| needs: scope | |
| if: ${{ contains(fromJSON(needs.scope.outputs.owners), 'test-linux') }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - runner: ubuntu-24.04-arm | |
| architecture: arm64 | |
| guest_target: aarch64-unknown-linux-musl | |
| - runner: ubuntu-24.04 | |
| architecture: x86_64 | |
| guest_target: x86_64-unknown-linux-musl | |
| runs-on: ${{ matrix.runner }} | |
| steps: | |
| - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd | |
| - uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b | |
| - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 | |
| - run: uv sync --project build_system --frozen | |
| # `just` recipes reach `_pnpm-install`, which installs every Node | |
| # workspace. Without pnpm on PATH those recipes die with exit 127. | |
| # No `cache: pnpm`: `_gate-linux-rust` hands off to test-linux-rust.sh and | |
| # exits before `_pnpm-install`, so no store is ever created here and the | |
| # post-job save fails with "Path(s) ... do(es) not exist". Static recipe | |
| # reachability can justify installing a tool, never declaring a cache. | |
| - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 | |
| with: | |
| version: 10.34.5 | |
| - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 | |
| with: | |
| node-version: 24 | |
| - uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c | |
| with: | |
| toolchain: 1.97.1 | |
| components: llvm-tools | |
| targets: aarch64-unknown-linux-gnu,x86_64-unknown-linux-gnu,aarch64-unknown-linux-musl,x86_64-unknown-linux-musl | |
| - uses: ./.github/actions/rust-cache | |
| - name: Install Linux workspace lint prerequisites | |
| run: sudo python3 build_system/scripts/bootstrap/provision-linux-workspace.py --install apt | |
| # Try to enable KVM for integration tests. GitHub-hosted runners don't | |
| # always expose nested virt -- when /dev/kvm is absent the udev trigger | |
| # fails with "Failed to open the device 'kvm': Invalid argument". We | |
| # let that pass and fall through to a compile-only/no-KVM run; the | |
| # release pipeline owns real-KVM coverage. See sprints/done/ci-green. | |
| - name: Enable KVM (best-effort) | |
| continue-on-error: true | |
| run: | | |
| sudo bash build_system/packaging/shared/install-vm-device-access "$USER" build_system/packaging/linux/99-capsem-vm-devices.rules | |
| - name: Select the declared tool set | |
| id: gate_tools | |
| run: | | |
| list=$(python3 build_system/scripts/ci/gate-tool-list.py --sets coverage) | |
| echo "list=$list" >> "$GITHUB_OUTPUT" | |
| - name: Install prebuilt Rust tools | |
| uses: taiki-e/install-action@07b4745e0c39a41822af610387492e3e53aa222b | |
| with: | |
| tool: ${{ steps.gate_tools.outputs.list }} | |
| - name: Prepare test output owners | |
| run: mkdir -p cache/target/coverage/linux cache/target/tests/evidence | |
| # Library + service crate tests with coverage (capsem-core includes KVM backend on Linux). | |
| # capsem-app (Tauri shell) and capsem-tray (macOS muda menu-bar) are macOS-only; every | |
| # other host crate is portable and runs here so it gets Linux-specific regression coverage. | |
| - name: Unit tests (KVM backend) with coverage | |
| timeout-minutes: 45 | |
| run: just test-linux-rust | |
| # cargo check still runs BLAKE3's native SIMD build. Both guest targets | |
| # belong on their native Linux C toolchain, already provisioned above. | |
| # Build and link the production feature set, not a pure-Rust substitute. | |
| - name: Build native musl guest binaries | |
| env: | |
| GUEST_TARGET: ${{ matrix.guest_target }} | |
| CC_aarch64_unknown_linux_musl: musl-gcc | |
| CC_x86_64_unknown_linux_musl: musl-gcc | |
| run: cargo build --locked --release --target "$GUEST_TARGET" -p capsem-agent | |
| - name: Upload Linux coverage | |
| if: ${{ !cancelled() }} | |
| uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac | |
| with: | |
| files: cache/target/coverage/linux/codecov.json | |
| flags: linux-unit | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| fail_ci_if_error: false | |
| - name: Upload Linux test results to Codecov | |
| if: ${{ !cancelled() }} | |
| uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 | |
| with: | |
| files: cache/target/coverage/linux/nextest/ci/junit.xml | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| fail_ci_if_error: false | |
| # Note KVM exercise status. Hosted ARM runners may lack /dev/kvm; the | |
| # compile-only path still catches Linux build/lint regressions, and | |
| # real-KVM coverage runs in the release pipeline. Surfacing as a | |
| # warning (not an error) keeps CI honest about what was actually | |
| # exercised without false-failing on a runner-fleet limitation. | |
| - name: Note KVM exercise status | |
| run: | | |
| if [ -e /dev/kvm ]; then | |
| echo "KVM is available at /dev/kvm -- KVM-backed tests exercised." | |
| else | |
| echo "::warning::/dev/kvm not available on this runner -- compile + non-KVM tests only. Real-KVM coverage runs in release pipeline." | |
| fi | |
| - name: Test summary | |
| if: always() | |
| run: | | |
| KVM_STATUS="available" | |
| [ -e /dev/kvm ] || KVM_STATUS="not available" | |
| COV=$(grep 'TOTAL' cache/target/coverage/linux/summary.txt 2>/dev/null | awk '{print $(NF)}' || echo "?") | |
| cat >> "$GITHUB_STEP_SUMMARY" << EOF | |
| ## Linux Test Results | |
| | Metric | Result | | |
| |--------|--------| | |
| | Runner | ${{ matrix.runner }} (${{ matrix.architecture }}) | | |
| | Guest target | ${{ matrix.guest_target }} | | |
| | /dev/kvm | $KVM_STATUS | | |
| | Line coverage | $COV | | |
| | KVM backend | compiled (real-KVM tests run only when /dev/kvm is present) | | |
| EOF | |
| # T5: preserve test artifacts on failure (Linux job). | |
| - name: Upload test artifacts on failure (Linux) | |
| if: failure() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: test-artifacts-linux-${{ matrix.architecture }}-${{ github.run_attempt }} | |
| path: cache/target/tests/evidence/ | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| # --------------------------------------------------------------------------- | |
| # macOS: full test suite (Apple VZ backend, frontend, Python, coverage) | |
| # --------------------------------------------------------------------------- | |
| test: | |
| needs: scope | |
| if: ${{ contains(fromJSON(needs.scope.outputs.owners), 'test') }} | |
| runs-on: macos-14 | |
| steps: | |
| - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd | |
| # tests/capsem-release/ shells out to `just` to prove the release recipes | |
| # sequence their side effects; without it those contracts cannot run. | |
| - uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b | |
| - uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c | |
| with: | |
| toolchain: 1.97.1 | |
| targets: aarch64-unknown-linux-gnu,x86_64-unknown-linux-gnu,aarch64-unknown-linux-musl,x86_64-unknown-linux-musl | |
| components: llvm-tools | |
| - uses: ./.github/actions/rust-cache | |
| - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 | |
| with: | |
| version: 10.34.5 | |
| - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 | |
| with: | |
| node-version: 24 | |
| cache: pnpm | |
| cache-dependency-path: | | |
| web/app/pnpm-lock.yaml | |
| sdk/typescript/pnpm-lock.yaml | |
| build_system/release_site/pnpm-lock.yaml | |
| - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 | |
| - run: uv sync --project build_system --frozen | |
| - name: Build release administration CLI | |
| run: cargo build -p capsem-admin | |
| - name: macOS release portability preflight | |
| run: >- | |
| uv run --project build_system --frozen python -m pytest | |
| -c build_system/pyproject.toml --rootdir . | |
| tests/capsem-release/test_release_channel_contract.py::test_binary_staging_artifacts_are_deterministic_and_recordable | |
| -q | |
| - run: bash build_system/scripts/build/generate-settings.sh | |
| - run: bash build_system/scripts/test/prepare-install-test-assets.sh | |
| - run: cd web/app && pnpm install --frozen-lockfile | |
| - name: Build TypeScript SDK for frontend consumers | |
| working-directory: sdk/typescript | |
| run: | | |
| pnpm install --frozen-lockfile | |
| pnpm run build | |
| - name: Build frontend bundle | |
| run: bash build_system/scripts/web/check-web-surface.sh frontend-build | |
| - name: Install release site dependencies | |
| run: cd build_system/release_site && pnpm install --frozen-lockfile | |
| - name: Select the declared tool set | |
| id: gate_tools | |
| run: | | |
| list=$(python3 build_system/scripts/ci/gate-tool-list.py --sets coverage,digest) | |
| echo "list=$list" >> "$GITHUB_OUTPUT" | |
| - name: Install prebuilt Rust tools | |
| uses: taiki-e/install-action@07b4745e0c39a41822af610387492e3e53aa222b | |
| with: | |
| tool: ${{ steps.gate_tools.outputs.list }} | |
| - name: Prepare test output owners | |
| run: | | |
| mkdir -p cache/target/coverage/python cache/target/coverage/rust cache/target/coverage/junit cache/target/tests/evidence | |
| echo "COVERAGE_FILE=$GITHUB_WORKSPACE/cache/target/coverage/.coverage" >> "$GITHUB_ENV" | |
| - name: Install sha256sum compatibility wrapper | |
| run: | | |
| if ! command -v sha256sum >/dev/null 2>&1; then | |
| mkdir -p "$HOME/.local/bin" | |
| printf '%s\n' '#!/bin/sh' 'exec shasum -a 256 "$@"' > "$HOME/.local/bin/sha256sum" | |
| chmod +x "$HOME/.local/bin/sha256sum" | |
| echo "$HOME/.local/bin" >> "$GITHUB_PATH" | |
| fi | |
| # Unit tests: all crates with coverage + JUnit XML for test analytics. | |
| # capsem-app (Tauri bin) is macOS-only; capsem-mcp-aggregator and | |
| # capsem-mcp-builtin are thin binaries that pull capsem-core logic. | |
| - name: Unit tests with coverage | |
| run: | | |
| set -o pipefail | |
| cargo llvm-cov nextest --no-cfg-coverage --lib --bins --profile ci-unit --codecov --output-path cache/target/coverage/rust/unit.json -p capsem-api -p capsem-sdk -p capsem-archive -p capsem-telemetry -p capsem-assets -p capsem-config -p capsem-credentials -p capsem-foundation -p capsem-core -p capsem-admin -p capsem-agent -p capsem-logger -p capsem-proto -p capsem-guard -p capsem-gateway -p capsem-service -p capsem -p capsem-tui -p capsem-router -p capsem-network -p capsem-mcp-aggregator -p capsem-mcp-builtin -p capsem-tray -p capsem-app -p capsem-process -p capsem-bench -p capsem-mock-server | |
| cargo llvm-cov report --summary-only -p capsem-api -p capsem-sdk -p capsem-archive -p capsem-telemetry -p capsem-assets -p capsem-config -p capsem-credentials -p capsem-foundation -p capsem-core -p capsem-admin -p capsem-agent -p capsem-logger -p capsem-proto -p capsem-guard -p capsem-gateway -p capsem-service -p capsem -p capsem-tui -p capsem-router -p capsem-network -p capsem-mcp-aggregator -p capsem-mcp-builtin -p capsem-tray -p capsem-app -p capsem-process -p capsem-bench -p capsem-mock-server 2>&1 | tee cache/target/coverage/rust/summary.txt | |
| # Integration tests (tests/ directory, cross-crate) | |
| - name: Integration tests with coverage | |
| run: | | |
| cargo llvm-cov nextest --no-cfg-coverage --profile ci-integration --codecov --output-path cache/target/coverage/rust/integration.json -p capsem-core --test '*' | |
| # Frontend tests with coverage + JUnit output. The bundle this used to | |
| # build as a third stage comes from the `frontend-build` job above, and | |
| # nothing in this job reads it. | |
| - name: Frontend type-check and test | |
| env: | |
| CAPSEM_FRONTEND_JUNIT: ${{ github.workspace }}/cache/target/coverage/junit/frontend.xml | |
| run: bash build_system/scripts/web/check-web-surface.sh frontend-verify | |
| # Python schema tests with coverage | |
| - name: Python SDK tests with coverage | |
| working-directory: sdk/python | |
| env: | |
| COVERAGE_FILE: ${{ github.workspace }}/cache/target/coverage/python-sdk/.coverage | |
| run: uv run --frozen pytest --junitxml=../../cache/target/coverage/junit/python-sdk.xml | |
| - name: Inspect AI extension tests with coverage | |
| working-directory: integrations/inspect-ai | |
| env: | |
| COVERAGE_FILE: ${{ github.workspace }}/cache/target/coverage/inspect-ai/.coverage | |
| run: uv run --frozen pytest --junitxml=../../cache/target/coverage/junit/inspect-ai.xml | |
| - name: Python lint and type check | |
| run: | | |
| uv run --project build_system --frozen capsem-gate lint | |
| uv run --project build_system --frozen capsem-builder validate-skills skills | |
| - name: TypeScript SDK tests with coverage | |
| working-directory: sdk/typescript | |
| run: | | |
| pnpm install --frozen-lockfile | |
| pnpm test --reporter=default --reporter=junit --outputFile=../../cache/target/coverage/junit/typescript-sdk.xml | |
| # Builds against the SDK package the step above just built. | |
| - name: MCP server tests with coverage | |
| working-directory: mcp/typescript | |
| run: | | |
| pnpm install --frozen-lockfile | |
| pnpm test --reporter=default --reporter=junit --outputFile=../../cache/target/coverage/junit/mcp-typescript.xml | |
| - name: Cross-system Python schema tests with coverage | |
| run: >- | |
| uv run --project build_system --frozen python -m pytest -c build_system/pyproject.toml --rootdir . | |
| tests/citadel/test_agent_skill_index.py | |
| tests/test_capsem_bench_mock_server_protocol.py | |
| tests/test_capsem_bench_storage.py | |
| tests/test_settings_spec.py | |
| tests/capsem-rootfs-artifacts/test_rootfs_artifacts.py | |
| --cov=build_system/builder --cov-report= --cov-fail-under=0 | |
| --junitxml=cache/target/coverage/junit/python-cross-system.xml | |
| - name: Build-system Python schema tests with coverage | |
| run: >- | |
| uv run --project build_system --frozen python -m pytest -c build_system/pyproject.toml --rootdir . | |
| build_system/tests/image/test_audit.py | |
| build_system/tests/packaging/test_build_pkg.py | |
| build_system/tests/image/test_cli.py | |
| build_system/tests/image/test_config.py | |
| build_system/tests/image/test_docker.py | |
| build_system/tests/image/test_doctor.py | |
| build_system/tests/image/test_image_build_backend.py | |
| build_system/tests/image/test_manifest.py | |
| build_system/tests/scripts/test_mock_server_launcher.py | |
| build_system/tests/image/test_models.py | |
| build_system/tests/scripts/test_protocol_fixture_recorder.py | |
| build_system/tests/packaging/test_repack_deb.py | |
| build_system/tests/image/test_skills.py | |
| build_system/tests/image/test_validate.py | |
| build_system/tests/scripts/test_clean_stale.py | |
| --cov=build_system/builder --cov-append --cov-report= --cov-fail-under=0 | |
| --junitxml=cache/target/coverage/junit/python-build-system.xml | |
| # Python integration tests that need no VM | |
| - name: Python integration tests (non-VM suites) | |
| run: | | |
| bash build_system/scripts/test/prepare-install-test-assets.sh | |
| cargo build -p capsem-process -p capsem-service -p capsem -p capsem-mock-server -p capsem-bench | |
| for bin in cache/target/cargo/debug/capsem-process cache/target/cargo/debug/capsem-service cache/target/cargo/debug/capsem cache/target/cargo/debug/capsem-mock-server cache/target/cargo/debug/capsem-bench-rs; do | |
| codesign --sign - --entitlements build_system/packaging/macos/entitlements.plist --force "$bin" | |
| done | |
| uv run --project build_system --frozen python -m pytest -c build_system/pyproject.toml --rootdir . tests/capsem-bootstrap/ \ | |
| tests/capsem-codesign/ \ | |
| tests/capsem-rootfs-artifacts/ \ | |
| tests/capsem-release/ \ | |
| -v --tb=short \ | |
| --cov=build_system/builder \ | |
| --cov-append \ | |
| --cov-report=xml:cache/target/coverage/python/codecov.xml \ | |
| --cov-fail-under=0 | |
| # Verify all integration test suites import cleanly (catches broken imports/syntax) | |
| - name: Verify all integration test imports | |
| run: | | |
| uv run --project build_system --frozen python -m pytest -c build_system/pyproject.toml --rootdir . tests/capsem-*/ --collect-only -q | |
| # Schema drift check | |
| - name: Schema drift check | |
| run: | | |
| uv run --project build_system --frozen python build_system/scripts/build/generate_schema.py | |
| git diff --exit-code config/settings/schema.generated.json \ | |
| config/settings/ui-metadata.generated.json \ | |
| web/app/src/lib/mock-settings.generated.ts | |
| # Upload coverage with flags | |
| - name: Upload TypeScript SDK coverage | |
| if: ${{ !cancelled() }} | |
| uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac | |
| with: | |
| files: cache/target/coverage/typescript-sdk/lcov.info | |
| flags: typescript-sdk | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| fail_ci_if_error: false | |
| - name: Upload MCP server coverage | |
| if: ${{ !cancelled() }} | |
| uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac | |
| with: | |
| files: cache/target/coverage/mcp-typescript/lcov.info | |
| flags: mcp-server | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| fail_ci_if_error: false | |
| - name: Upload Python SDK coverage | |
| if: ${{ !cancelled() }} | |
| uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac | |
| with: | |
| files: cache/target/coverage/python-sdk/coverage.xml | |
| flags: python-sdk | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| fail_ci_if_error: false | |
| - name: Upload Inspect AI extension coverage | |
| if: ${{ !cancelled() }} | |
| uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac | |
| with: | |
| files: cache/target/coverage/inspect-ai/coverage.xml | |
| flags: inspect-ai | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| fail_ci_if_error: false | |
| - name: Upload Rust unit test coverage | |
| if: ${{ !cancelled() }} | |
| uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac | |
| with: | |
| files: cache/target/coverage/rust/unit.json | |
| flags: unit | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| fail_ci_if_error: false | |
| - name: Upload Rust integration test coverage | |
| if: ${{ !cancelled() }} | |
| uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac | |
| with: | |
| files: cache/target/coverage/rust/integration.json | |
| flags: integration | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| fail_ci_if_error: false | |
| - name: Upload frontend coverage | |
| if: ${{ !cancelled() }} | |
| uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac | |
| with: | |
| files: cache/target/coverage/web-app/coverage-final.json | |
| flags: unit | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| fail_ci_if_error: false | |
| - name: Upload Python coverage | |
| if: ${{ !cancelled() }} | |
| uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac | |
| with: | |
| files: cache/target/coverage/python/codecov.xml | |
| flags: unit | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| fail_ci_if_error: false | |
| # Upload test results for test analytics | |
| - name: Upload test results to Codecov | |
| if: ${{ !cancelled() }} | |
| uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 | |
| with: | |
| files: cache/target/coverage/nextest/ci-unit/junit.xml,cache/target/coverage/nextest/ci-integration/junit.xml,cache/target/coverage/junit/frontend.xml,cache/target/coverage/junit/python-cross-system.xml,cache/target/coverage/junit/python-build-system.xml,cache/target/coverage/junit/python-sdk.xml,cache/target/coverage/junit/inspect-ai.xml,cache/target/coverage/junit/typescript-sdk.xml,cache/target/coverage/junit/mcp-typescript.xml | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| fail_ci_if_error: false | |
| # T5: preserve every test artifact (service.log / process.log / | |
| # session.db etc.) on failure so PR reviewers can debug without | |
| # rerunning. preserve_tmp_dir_on_failure() in tests/helpers/service.py | |
| # populates `cache/target/tests/evidence/` only on red runs; if-no-files-found | |
| # is "ignore" so green runs don't bloat the workflow. | |
| - name: Upload test artifacts on failure | |
| if: failure() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: test-artifacts-${{ runner.os }}-${{ github.run_attempt }} | |
| path: cache/target/tests/evidence/ | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| - name: Test summary | |
| if: always() | |
| run: | | |
| COV=$(grep 'TOTAL' cache/target/coverage/rust/summary.txt 2>/dev/null | awk '{print $(NF)}' || echo "?") | |
| cat >> "$GITHUB_STEP_SUMMARY" << EOF | |
| ## Test Results | |
| | Metric | Result | | |
| |--------|--------| | |
| | Line coverage | $COV | | |
| | Test results | See Codecov test analytics | | |
| | Guest compilation | ARM64 and x86_64 musl builds owned by test-linux | | |
| | Audit | npm bulk advisory API (RustSec runs in security-audit.yaml) | | |
| > Coverage covers library crates + gateway (capsem-core, agent, logger, proto, gateway). | |
| > Full workspace coverage runs in the release pipeline. | |
| EOF | |
| # --------------------------------------------------------------------------- | |
| # Install e2e: Docker-based install layout + systemd tests (Linux) | |
| # --------------------------------------------------------------------------- | |
| test-install: | |
| needs: scope | |
| if: ${{ contains(fromJSON(needs.scope.outputs.owners), 'test-install') }} | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd | |
| - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 | |
| # The package source build compiles the musl guest cohort and therefore | |
| # needs the same C toolchain as the release asset builder. | |
| - name: Install musl C toolchain | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends musl-tools acl | |
| - name: Enable required KVM and vsock devices | |
| run: | | |
| sudo bash build_system/packaging/shared/install-vm-device-access "$USER" build_system/packaging/linux/99-capsem-vm-devices.rules | |
| test -c /dev/kvm | |
| test -c /dev/vhost-vsock | |
| - name: Classify the stable install graph | |
| id: install-channel | |
| run: | | |
| python3 build_system/scripts/bootstrap/select-runtime-preflight-manifest.py \ | |
| --channel stable \ | |
| --classify-only \ | |
| --github-output "$GITHUB_OUTPUT" | |
| - name: Select exact stable install inputs | |
| id: install-manifest | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| PUBLIC_MANIFEST_URL: ${{ steps.install-channel.outputs.manifest-url }} | |
| PUBLIC_STATE: ${{ steps.install-channel.outputs.state }} | |
| run: | | |
| set -euo pipefail | |
| case "$PUBLIC_STATE" in | |
| published) | |
| echo "manifest-url=$PUBLIC_MANIFEST_URL" >> "$GITHUB_OUTPUT" | |
| ;; | |
| retired) | |
| # The package derives public/corporate lock policy from this | |
| # canonical URL shape. Keep a retired first-party fixture public | |
| # so the install gate may hand it its generated local manifest. | |
| output="$PWD/cache/target/ci-install-selection/assets/stable/manifest.json" | |
| python3 build_system/scripts/release/fetch-channel-source-manifest.py \ | |
| --channel stable \ | |
| --repository "$GITHUB_REPOSITORY" \ | |
| --require-runtime \ | |
| --output "$output" | |
| echo "manifest-url=file://$output" >> "$GITHUB_OUTPUT" | |
| ;; | |
| *) | |
| echo "stable install graph is $PUBLIC_STATE; no exact install input exists" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| - name: Pull exact stable runtime assets | |
| uses: ./.github/actions/fetch-release-inputs | |
| with: | |
| manifest-url: ${{ steps.install-manifest.outputs.manifest-url }} | |
| kind: runtime | |
| architecture: x86_64 | |
| output: cache/target/ci-install-content/inputs | |
| - name: Stage the exact install assets | |
| run: | | |
| uv run --project build_system --frozen python build_system/scripts/release/stage-release-test-inputs.py \ | |
| --input-dir cache/target/ci-install-content/inputs \ | |
| --assets-dir cache/target/ci-install-content/assets | |
| - name: Build exact native release package | |
| env: | |
| CAPSEM_INSTALL_MANIFEST_URL: ${{ steps.install-manifest.outputs.manifest-url }} | |
| CAPSEM_INSTALL_CHANNEL: stable | |
| run: | | |
| uv run --project build_system --frozen capsem-gate cross-compile x86_64 \ | |
| --content-root cache/target/ci-install-content \ | |
| --defer-proof | |
| - name: Run install e2e tests | |
| id: install_e2e | |
| run: | | |
| uv run --project build_system --frozen capsem-gate install \ | |
| --selected-content-root cache/target/ci-install-content | |
| - name: Upload install and glow-up evidence on failure | |
| if: failure() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: install-glowup-evidence-${{ github.run_attempt }} | |
| path: | | |
| cache/target/gate-runs/ | |
| cache/target/release/staging/local-glowup-evidence/ | |
| retention-days: 7 | |
| # Input resolution can fail before a gate exists. Preserve that | |
| # primary failure without adding a misleading upload failure, while | |
| # keeping missing evidence fatal when the install gate itself ran. | |
| if-no-files-found: ${{ steps.install_e2e.outcome == 'failure' && 'error' || 'warn' }} | |
| # --------------------------------------------------------------------------- | |
| # Web: build docs and marketing before pr-gate can pass | |
| # --------------------------------------------------------------------------- | |
| docs-build: | |
| needs: scope | |
| if: ${{ contains(fromJSON(needs.scope.outputs.owners), 'docs-build') }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd | |
| - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 | |
| with: | |
| version: 10.34.5 | |
| - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 | |
| with: | |
| node-version: 24 | |
| cache: pnpm | |
| cache-dependency-path: web/docs/pnpm-lock.yaml | |
| - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 | |
| - name: Install docs dependencies | |
| run: cd web/docs && pnpm install --frozen-lockfile | |
| - name: Build docs | |
| run: bash build_system/scripts/web/check-web-surface.sh docs | |
| site-build: | |
| needs: scope | |
| if: ${{ contains(fromJSON(needs.scope.outputs.owners), 'site-build') }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd | |
| - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 | |
| with: | |
| version: 10.34.5 | |
| - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 | |
| with: | |
| node-version: 24 | |
| cache: pnpm | |
| cache-dependency-path: web/marketing/pnpm-lock.yaml | |
| - name: Install site dependencies | |
| run: cd web/marketing && pnpm install --frozen-lockfile | |
| - name: Build site | |
| run: bash build_system/scripts/web/check-web-surface.sh site | |
| release-site-build: | |
| needs: scope | |
| if: ${{ contains(fromJSON(needs.scope.outputs.owners), 'release-site-build') }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd | |
| - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 | |
| with: | |
| version: 10.34.5 | |
| - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 | |
| with: | |
| node-version: 24 | |
| cache: pnpm | |
| cache-dependency-path: build_system/release_site/pnpm-lock.yaml | |
| - uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c | |
| with: | |
| toolchain: 1.97.1 | |
| - uses: ./.github/actions/rust-cache | |
| with: | |
| key: release-site-build | |
| - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 | |
| - name: Install Python dependencies | |
| run: uv sync --project build_system --frozen | |
| - name: Install release site dependencies | |
| run: cd build_system/release_site && pnpm install --frozen-lockfile | |
| - name: Release site type-check and coverage | |
| run: bash build_system/scripts/web/check-web-surface.sh release-site | |
| - name: Release channel build parity | |
| run: bash build_system/scripts/web/check-web-surface.sh release-channel | |
| - name: Upload release site coverage | |
| if: ${{ !cancelled() }} | |
| uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac | |
| with: | |
| files: cache/target/coverage/distribution-site/lcov.info | |
| flags: unit | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| fail_ci_if_error: false | |
| # --------------------------------------------------------------------------- | |
| # Stable branch-protection check: one required status for the real PR gate. | |
| # --------------------------------------------------------------------------- | |
| pr-gate: | |
| runs-on: ubuntu-latest | |
| needs: [scope, fast-gate, test-linux, test, test-install, docs-build, site-build, release-site-build] | |
| if: ${{ always() }} | |
| steps: | |
| - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd | |
| - name: Require all CI jobs | |
| env: | |
| CI_OWNERS: ${{ needs.scope.outputs.owners }} | |
| SCOPE_RESULT: ${{ needs.scope.result }} | |
| FAST_GATE_RESULT: ${{ needs.fast-gate.result }} | |
| TEST_LINUX_RESULT: ${{ needs.test-linux.result }} | |
| TEST_MACOS_RESULT: ${{ needs.test.result }} | |
| TEST_INSTALL_RESULT: ${{ needs.test-install.result }} | |
| DOCS_BUILD_RESULT: ${{ needs.docs-build.result }} | |
| SITE_BUILD_RESULT: ${{ needs.site-build.result }} | |
| RELEASE_SITE_BUILD_RESULT: ${{ needs.release-site-build.result }} | |
| TEST_LINUX_REQUIRED: ${{ contains(fromJSON(needs.scope.outputs.owners), 'test-linux') }} | |
| TEST_MACOS_REQUIRED: ${{ contains(fromJSON(needs.scope.outputs.owners), 'test') }} | |
| TEST_INSTALL_REQUIRED: ${{ contains(fromJSON(needs.scope.outputs.owners), 'test-install') }} | |
| DOCS_BUILD_REQUIRED: ${{ contains(fromJSON(needs.scope.outputs.owners), 'docs-build') }} | |
| SITE_BUILD_REQUIRED: ${{ contains(fromJSON(needs.scope.outputs.owners), 'site-build') }} | |
| RELEASE_SITE_BUILD_REQUIRED: ${{ contains(fromJSON(needs.scope.outputs.owners), 'release-site-build') }} | |
| run: bash build_system/scripts/ci/require-ci-jobs.sh |