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@v6
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@v6
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@v6
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@v6
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@v6
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@v8
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@v6
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@v6
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@v7
with:
name: decapod-debug-linux-${{ github.sha }}
path: target/debug/decapod
retention-days: 1
compression-level: 0
test:
name: Tests (${{ matrix.target }})
runs-on: ubuntu-latest
needs: setup
if: github.event_name == 'pull_request'
strategy:
fail-fast: false
matrix:
target: [core, plugins, cli, integration]
steps:
- uses: actions/checkout@v6
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: Detect changed files
id: changed
run: |
base_ref="${{ github.base_ref }}"
git fetch --no-tags --prune origin "$base_ref"
chg=$(git diff --name-only "origin/$base_ref"...HEAD | tr '\n' ',' | sed 's/,$//')
echo "changed=$chg" >> "$GITHUB_OUTPUT"
- name: Run selective tests
run: |
set -euo pipefail
CHANGED="${{ steps.changed.outputs.changed }}"
echo "Changed files: $CHANGED"
# decapod impact analysis
decapod impact --changed-files "$CHANGED" --predict --format text || true
case "${{ matrix.target }}" in
core)
if echo "$CHANGED" | grep -qE 'src/core/(todo|validate|workspace|workunit|obligation|docs|capsule|rpc|migration|gateway)'; then
cargo test --all-features --test todo_enforcement -- --test-threads=4
cargo test --all-features --test validate_termination -- --test-threads=4
cargo test --all-features --test workspace_interlock -- --test-threads=4
cargo test --all-features --test workunit_cli -- --test-threads=4
cargo test --all-features --test context_capsule_cli -- --test-threads=4
elif echo "$CHANGED" | grep -qE 'src/core/'; then
cargo test --all-features --test entrypoint_correctness -- --test-threads=4
fi
;;
plugins)
if echo "$CHANGED" | grep -qE 'src/plugins/'; then
cargo test --all-features --test plugins_todo_tests -- --test-threads=4
cargo test --all-features --test plugins_policy_tests -- --test-threads=4
cargo test --all-features --test plugins_health_tests -- --test-threads=4
cargo test --all-features --test plugins_aptitude_tests -- --test-threads=4
cargo test --all-features --test plugins_internalize_tests -- --test-threads=4
cargo test --all-features --test plugins_federation_tests -- --test-threads=4
cargo test --all-features --test plugins_decide_tests -- --test-threads=4
cargo test --all-features --test plugins_obligation_tests -- --test-threads=4
fi
;;
cli)
if echo "$CHANGED" | grep -qE 'src/(cli|lib|main)'; then
cargo test --all-features --test cli_contract_enforcement -- --test-threads=4
cargo test --all-features --test entrypoint_correctness -- --test-threads=4
cargo test --all-features --test cli_contracts -- --test-threads=4
fi
;;
integration)
if echo "$CHANGED" | grep -qE 'src/'; then
cargo test --all-features --test contract_conformance -- --test-threads=4
cargo test --all-features --test constitution_claims -- --test-threads=4
cargo test --all-features --test spec_conformance -- --test-threads=4
cargo test --all-features --test substrate_integrity -- --test-threads=4
fi
;;
esac
echo "✓ Selective tests passed"
test-master:
name: Tests (Full)
runs-on: ubuntu-latest
needs: setup
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@v6
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: Run all tests
run: cargo test --all-features --test gatling -- --test-threads=4
test-rpc-suite:
name: Tests (RPC Suite)
runs-on: ubuntu-latest
needs: setup
steps:
- uses: actions/checkout@v6
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@v8
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@v6
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@v8
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@v6
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@v8
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@v6
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@v8
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@v6
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@v8
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, test-rpc-suite, chaos-replay, daemonless-lifecycle, contracts, health]
steps:
- uses: actions/checkout@v6
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@v7
with:
name: decapod-${{ github.sha }}
path: target/release/decapod
retention-days: 1
compression-level: 9