name: CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
permissions:
contents: write
id-token: write
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0 CARGO_NET_RETRY: 10 CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse RUST_BACKTRACE: 1
RUSTFLAGS: "-C link-arg=-fuse-ld=lld -D warnings" NIX_CONFIG: "experimental-features = nix-command flakes"
jobs:
commit-lint:
name: Commit Lint
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check commit messages
run: |
git log --format=%B origin/${{ github.base_ref }}..HEAD | while read -r msg; do
[ -z "$msg" ] && continue
if echo "$msg" | grep -qiE "^(wip|fixup|squash)"; then
echo "❌ ERROR: WIP commit found: $msg"
exit 1
fi
done
echo "✅ Commit messages look good"
detect-migration-script-changes:
name: Detect Migration Script Changes
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
outputs:
changed: ${{ steps.diff.outputs.changed }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Detect changed migration scripts
id: diff
run: |
set -euo pipefail
base_ref="${{ github.base_ref }}"
git fetch --no-tags --prune origin "$base_ref"
changed_files="$(git diff --name-only "origin/$base_ref"...HEAD)"
echo "$changed_files"
if echo "$changed_files" | grep -qE '^src/core/sql/.*\.sql$'; then
echo "changed=true" >> "$GITHUB_OUTPUT"
else
echo "changed=false" >> "$GITHUB_OUTPUT"
fi
fmt:
name: Lint (fmt)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
- name: Enable Nix cache
uses: DeterminateSystems/magic-nix-cache-action@main
- name: Format check
run: nix develop .#ci -c cargo fmt --all -- --check
clippy:
name: Lint (clippy)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
- name: Enable Nix cache
uses: DeterminateSystems/magic-nix-cache-action@main
- name: Restore cache
uses: Swatinem/rust-cache@v2
with:
shared-key: "ci-build"
save-if: "false"
- name: Clippy
run: nix develop .#ci -c cargo clippy --all-targets --all-features -- -D warnings
health:
name: Health Checks
runs-on: ubuntu-latest
needs: setup
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Restore cache
uses: Swatinem/rust-cache@v2
with:
shared-key: "ci-build"
save-if: "false"
- name: Download shared decapod binary
uses: actions/download-artifact@v4
with:
name: decapod-debug-linux-${{ github.sha }}
path: target/debug
- name: Install shared decapod binary
run: |
chmod +x target/debug/decapod
echo "$PWD/target/debug" >> "$GITHUB_PATH"
- name: Initialize Decapod
run: decapod init --force
env:
DECAPOD_VALIDATE_SKIP_GIT_GATES: "1"
- name: Acquire Decapod session
run: decapod session acquire
env:
DECAPOD_VALIDATE_SKIP_GIT_GATES: "1"
- name: Run validation harness
run: decapod validate
env:
DECAPOD_VALIDATE_SKIP_GIT_GATES: "1"
DECAPOD_VALIDATE_TIMEOUT_SECS: "120"
- name: Run watcher
run: decapod govern watcher run
env:
DECAPOD_VALIDATE_SKIP_GIT_GATES: "1"
- name: Check health summary
env:
DECAPOD_VALIDATE_SKIP_GIT_GATES: "1"
run: |
STATUS=$(decapod govern health summary)
echo "$STATUS"
if echo "$STATUS" | grep -q '"watcher_stale": true'; then
echo "::warning::Watcher is stale"
fi
state-commit-golden-vectors:
name: Golden Vectors (${{ matrix.os }})
runs-on: ${{ matrix.os }}
env:
DECAPOD_STATE_COMMIT_CI_JOB: state-commit-golden-vectors
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
submodules: recursive
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Verify golden vectors exist
run: |
echo "=== Verifying golden vectors exist ==="
test -f tests/golden/state_commit/v1/scope_record.cbor || exit 1
test -f tests/golden/state_commit/v1/scope_record_hash.txt || exit 1
test -f tests/golden/state_commit/v1/state_commit_root.txt || exit 1
echo "=== Expected values ==="
echo "scope_record_hash: 41d7e3729b6f4512887fb3cb6f10140942b600041e0d88308b0177e06ebb4b93"
echo "state_commit_root: 28591ac86e52ffac76d5fc3aceeceda5d8592708a8d7fcb75371567fdc481492"
echo "=== Actual committed values ==="
cat tests/golden/state_commit/v1/scope_record_hash.txt
cat tests/golden/state_commit/v1/state_commit_root.txt
- name: Build golden vectors generator
run: cd tests/golden_vectors && unset RUSTFLAGS && cargo build --release
- name: Initialize fixture repo
run: |
cd tests/fixtures/state_commit_repo
git init
git config user.email "test@test.com"
git config user.name "Test"
git add initial.txt
git commit -m "base commit"
git add .
git commit -m "fixture commit"
- name: Run golden vectors generator
working-directory: tests/golden_vectors
run: |
./target/release/golden_vectors > /tmp/generated.txt 2>&1
GEN_HASH=$(grep "scope_record_hash:" /tmp/generated.txt | awk '{print $2}')
GEN_ROOT=$(grep "state_commit_root:" /tmp/generated.txt | awk '{print $2}')
echo "Generated hash: $GEN_HASH"
echo "Generated root: $GEN_ROOT"
test -n "$GEN_HASH" || exit 1
test -n "$GEN_ROOT" || exit 1
echo "✅ Golden vectors generator runs successfully"
- name: Verify no network dependencies
run: |
if grep -qE "(reqwest|hyper|ureq|http)" tests/golden_vectors/Cargo.toml; then
echo "ERROR: golden_vectors has network dependencies"
exit 1
fi
echo "✅ No network dependencies"
setup:
name: Setup & Build Shared Binary
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Install LLD
uses: ./.github/actions/install-lld
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Save Rust cache (always, so PRs benefit on next run)
uses: Swatinem/rust-cache@v2
with:
shared-key: "ci-build"
cache-targets: true
save-if: "true"
cache-all-crates: true
- name: Check Cargo.lock is up to date
run: cargo generate-lockfile && git diff --quiet Cargo.lock || echo "⚠️ Cargo.lock outdated - run cargo update locally"
- name: Compile shared decapod binary
run: cargo build --all-features --bin decapod
- name: Upload shared decapod binary
uses: actions/upload-artifact@v4
with:
name: decapod-debug-linux-${{ github.sha }}
path: target/debug/decapod
retention-days: 1
compression-level: 0
test-core:
name: Tests (Core)
runs-on: ubuntu-latest
needs: setup
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Install LLD
uses: ./.github/actions/install-lld
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Restore cache
uses: Swatinem/rust-cache@v2
with:
shared-key: "ci-build"
save-if: "false"
- name: Download shared decapod binary
uses: actions/download-artifact@v4
with:
name: decapod-debug-linux-${{ github.sha }}
path: target/debug
- name: Make shared decapod binary executable
run: chmod +x target/debug/decapod
- name: Run unit tests
run: cargo test --all-features --lib --bins -- --test-threads=4
test-integration:
name: Tests (Integration ${{ matrix.shard }}/20)
runs-on: ubuntu-latest
needs: setup
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Install LLD
uses: ./.github/actions/install-lld
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Restore cache
uses: Swatinem/rust-cache@v2
with:
shared-key: "ci-build"
save-if: "false"
- name: Download shared decapod binary
uses: actions/download-artifact@v4
with:
name: decapod-debug-linux-${{ github.sha }}
path: target/debug
- name: Make shared decapod binary executable
run: chmod +x target/debug/decapod
- name: Run integration tests (shard ${{ matrix.shard }}/20)
run: |
set -euo pipefail
shard=${{ matrix.shard }}
total=20
# All test targets: auto-discovered top-level tests + custom [[test]] targets
# Build the full list from Cargo.toml to include plugins_*, core_tests, etc.
mapfile -t all_tests < <(
{
# Auto-discovered: tests/*.rs
ls tests/*.rs | xargs -n1 basename | sed 's/\.rs$//'
# Custom [[test]] targets from Cargo.toml
grep -A1 '^\[\[test\]\]' Cargo.toml | grep '^name' | awk -F'"' '{print $2}'
} | sort -u
)
# Isolated suites have dedicated jobs
ISOLATED="agent_rpc_suite chaos_replay daemonless_lifecycle"
ran=0
for i in "${!all_tests[@]}"; do
t="${all_tests[$i]}"
# Skip isolated suites
if echo "$ISOLATED" | grep -qw "$t"; then
continue
fi
if [ $(( (i % total) + 1 )) -ne "$shard" ]; then
continue
fi
echo "Shard $shard running: $t"
cargo test --all-features --test "$t" -- --test-threads=4
ran=$(( ran + 1 ))
done
if [ "$ran" -eq 0 ]; then
echo "Shard $shard: no tests assigned"
fi
test-rpc-suite:
name: Tests (RPC Suite)
runs-on: ubuntu-latest
needs: setup
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
- name: Enable Nix cache
uses: DeterminateSystems/magic-nix-cache-action@main
- name: Restore cache
uses: Swatinem/rust-cache@v2
with:
shared-key: "ci-build"
save-if: "false"
- name: Download shared decapod binary
uses: actions/download-artifact@v4
with:
name: decapod-debug-linux-${{ github.sha }}
path: target/debug
- name: Make shared decapod binary executable
run: chmod +x target/debug/decapod
- name: Run RPC suite
run: nix develop .#ci -c cargo test --all-features --test agent_rpc_suite -- --test-threads=1
chaos-replay:
name: Chaos Replay Gate
runs-on: ubuntu-latest
needs: setup
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Install LLD
uses: ./.github/actions/install-lld
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Restore cache
uses: Swatinem/rust-cache@v2
with:
shared-key: "ci-build"
save-if: "false"
- name: Download shared decapod binary
uses: actions/download-artifact@v4
with:
name: decapod-debug-linux-${{ github.sha }}
path: target/debug
- name: Make shared decapod binary executable
run: chmod +x target/debug/decapod
- name: Run chaos replay determinism test
run: cargo test --test chaos_replay -- --test-threads=1
daemonless-lifecycle:
name: Daemonless Lifecycle Gate
runs-on: ubuntu-latest
needs: setup
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Install LLD
uses: ./.github/actions/install-lld
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Restore cache
uses: Swatinem/rust-cache@v2
with:
shared-key: "ci-build"
save-if: "false"
- name: Download shared decapod binary
uses: actions/download-artifact@v4
with:
name: decapod-debug-linux-${{ github.sha }}
path: target/debug
- name: Make shared decapod binary executable
run: chmod +x target/debug/decapod
- name: Run daemonless lifecycle test
run: cargo test --all-features --test daemonless_lifecycle -- --test-threads=1
contracts:
name: Contract Conformance
runs-on: ubuntu-latest
needs: setup
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Install LLD
uses: ./.github/actions/install-lld
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Restore cache
uses: Swatinem/rust-cache@v2
with:
shared-key: "ci-build"
save-if: "false"
- name: Download shared decapod binary
uses: actions/download-artifact@v4
with:
name: decapod-debug-linux-${{ github.sha }}
path: target/debug
- name: Make shared decapod binary executable
run: chmod +x target/debug/decapod
- name: Run contract conformance tests
run: |
cargo test --all-features --test contract_conformance -- --test-threads=1
cargo test --all-features --test constitution_claims -- --test-threads=1
- name: Run CLI contract enforcement tests
run: cargo test --all-features --test cli_contract_enforcement -- --test-threads=1
migration-script-tests:
name: Migration Script Tests
runs-on: ubuntu-latest
needs: [setup, detect-migration-script-changes]
if: github.event_name == 'pull_request' && needs.detect-migration-script-changes.outputs.changed == 'true'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Install LLD
uses: ./.github/actions/install-lld
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Restore cache
uses: Swatinem/rust-cache@v2
with:
shared-key: "ci-build"
save-if: "false"
- name: Download shared decapod binary
uses: actions/download-artifact@v4
with:
name: decapod-debug-linux-${{ github.sha }}
path: target/debug
- name: Make shared decapod binary executable
run: chmod +x target/debug/decapod
- name: Run migration tests
run: cargo test --all-features --test core_tests migration_ -- --ignored --test-threads=1
release-build:
name: Release Build Check
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master'
needs: [fmt, clippy, test-core, test-integration, test-rpc-suite, chaos-replay, daemonless-lifecycle, contracts, health]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Install LLD
uses: ./.github/actions/install-lld
- uses: dtolnay/rust-toolchain@stable
- name: Restore cache
uses: Swatinem/rust-cache@v2
with:
shared-key: "release"
cache-targets: true
save-if: true
- name: Build release
run: cargo build --release --locked
- name: Upload binary
uses: actions/upload-artifact@v4
with:
name: decapod-${{ github.sha }}
path: target/release/decapod
retention-days: 1
compression-level: 9