Skip to content

feat(inspect-ai): add OCI container execution mode and Compose parser #1412

feat(inspect-ai): add OCI container execution mode and Compose parser

feat(inspect-ai): add OCI container execution mode and Compose parser #1412

Workflow file for this run

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