/ .github / workflows / ci.yml
ci.yml
  1  name: CI - Effects System Enforcement
  2  
  3  on:
  4    push:
  5      branches: [main, develop]
  6    pull_request:
  7      branches: [main, develop]
  8  
  9  env:
 10    CARGO_TERM_COLOR: always
 11    RUST_BACKTRACE: 1
 12  
 13  jobs:
 14    format:
 15      name: Format Check
 16      runs-on: ubuntu-latest
 17      steps:
 18        - uses: actions/checkout@v4
 19        - name: Install Rust
 20          uses: actions-rust-lang/setup-rust-toolchain@v1
 21          with:
 22            components: rustfmt
 23        - name: Check formatting
 24          run: cargo fmt --all -- --check
 25  
 26    clippy-effects-enforcement:
 27      name: Clippy - Effects System Enforcement
 28      runs-on: ubuntu-latest
 29      steps:
 30        - uses: actions/checkout@v4
 31        - name: Install Rust
 32          uses: actions-rust-lang/setup-rust-toolchain@v1
 33          with:
 34            components: clippy
 35        - name: Cache cargo registry
 36          uses: actions/cache@v3
 37          with:
 38            path: ~/.cargo/registry
 39            key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
 40        - name: Cache cargo index
 41          uses: actions/cache@v3
 42          with:
 43            path: ~/.cargo/git
 44            key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
 45        - name: Cache cargo build
 46          uses: actions/cache@v3
 47          with:
 48            path: target
 49            key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
 50        - name: Run clippy with effects enforcement
 51          run: |
 52            cargo clippy --workspace --all-targets --verbose -- \
 53              -D warnings \
 54              -D clippy::disallowed_methods \
 55              -D clippy::disallowed_types \
 56              -D clippy::unwrap_used \
 57              -D clippy::expect_used \
 58              -D clippy::duplicated_attributes
 59  
 60    test:
 61      name: Test Suite
 62      runs-on: ubuntu-latest
 63      steps:
 64        - uses: actions/checkout@v4
 65        - name: Install Rust
 66          uses: actions-rust-lang/setup-rust-toolchain@v1
 67        - name: Install Quint
 68          run: npm install -g @informalsystems/quint@0.25.1
 69        - name: Cache cargo registry
 70          uses: actions/cache@v3
 71          with:
 72            path: ~/.cargo/registry
 73            key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
 74        - name: Cache cargo index
 75          uses: actions/cache@v3
 76          with:
 77            path: ~/.cargo/git
 78            key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
 79        - name: Cache cargo build
 80          uses: actions/cache@v3
 81          with:
 82            path: target
 83            key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
 84        - name: Run tests
 85          run: cargo test --workspace --verbose
 86  
 87    check-effects-violations:
 88      name: Check for Effects System Violations
 89      runs-on: ubuntu-latest
 90      steps:
 91        - uses: actions/checkout@v4
 92        - name: Install ripgrep
 93          run: sudo apt-get update && sudo apt-get install -y ripgrep
 94        - name: Check for direct time usage
 95          run: |
 96            echo "Checking for direct time usage violations..."
 97  
 98            # Get files with violations
 99            files_with_violations=$(rg --type rust "SystemTime::now|Instant::now|chrono::Utc::now" crates/ \
100                --files-with-matches \
101                --glob '!**/aura-agent/**' \
102                --glob '!**/aura-effects/**' \
103                --glob '!**/aura-simulator/**' \
104                --glob '!**/aura-testkit/**' \
105                --glob '!**/aura-terminal/**' \
106                --glob '!**/tests/**' \
107                --glob '!**/integration/**' \
108                --glob '!**/demo/**' 2>/dev/null || true)
109  
110            violations=""
111            for file in $files_with_violations; do
112              # Skip files with allow attribute in test modules
113              if grep -q "#\[cfg(test)\]" "$file" && \
114                 grep -A 5 "#\[cfg(test)\]" "$file" | grep -q "#!\[allow(clippy::disallowed_methods)\]"; then
115                continue
116              fi
117  
118              # Get violations from this file, excluding comments
119              file_violations=$(rg "SystemTime::now|Instant::now|chrono::Utc::now" "$file" --line-number 2>/dev/null | \
120                  grep -v '^\s*//' | \
121                  grep -v ':\s*//' || true)
122  
123              if [ -n "$file_violations" ]; then
124                violations="$violations$file_violations"$'\n'
125              fi
126            done
127  
128            if [ -n "$violations" ]; then
129              echo "$violations"
130              echo "[ERROR] Found direct time usage! Use effects.now() instead."
131              exit 1
132            else
133              echo "[OK] No direct time usage violations found"
134            fi
135        - name: Check for direct randomness usage
136          run: |
137            echo "Checking for direct randomness usage violations..."
138            if rg --type rust "rand::random|thread_rng\(\)|OsRng::new" crates/ --line-number \
139                --glob '!**/aura-agent/**' \
140                --glob '!**/aura-effects/**' \
141                --glob '!**/aura-simulator/**' \
142                --glob '!**/aura-testkit/**' \
143                --glob '!**/tests/**'; then
144              echo "[ERROR] Found direct randomness usage! Use effects.random_bytes() or effects.rng() instead."
145              exit 1
146            else
147              echo "[OK] No direct randomness usage violations found"
148            fi
149        - name: Check for direct UUID usage
150          run: |
151            echo "Checking for direct UUID usage violations..."
152  
153            # Get files with violations
154            files_with_violations=$(rg --type rust "Uuid::new_v4\(\)" crates/ \
155                --files-with-matches \
156                --glob '!**/aura-effects/**' \
157                --glob '!**/aura-agent/**' \
158                --glob '!**/aura-simulator/**' \
159                --glob '!**/aura-testkit/**' \
160                --glob '!**/aura-quint/**' \
161                --glob '!**/aura-terminal/**' \
162                --glob '!**/tests/**' \
163                --glob '!**/aura-core/src/types/identifiers.rs' \
164                --glob '!**/aura-core/src/effects/quint.rs' \
165                --glob '!**/aura-composition/src/registry.rs' \
166                --glob '!**/aura-sync/src/services/maintenance.rs' \
167                --glob '!**/aura-sync/src/infrastructure/peers.rs' 2>/dev/null || true)
168  
169            violations=""
170            for file in $files_with_violations; do
171              # Skip files with allow attribute in test modules
172              if grep -q "#\[cfg(test)\]" "$file" && \
173                 grep -A 5 "#\[cfg(test)\]" "$file" | grep -q "#\[allow(clippy::disallowed_methods)\]"; then
174                continue
175              fi
176  
177              # Get violations from this file, excluding comments
178              file_violations=$(rg "Uuid::new_v4\(\)" "$file" --line-number 2>/dev/null | \
179                  grep -v '^\s*//' | \
180                  grep -v ':\s*//' || true)
181  
182              if [ -n "$file_violations" ]; then
183                violations="$violations$file_violations"$'\n'
184              fi
185            done
186  
187            if [ -n "$violations" ]; then
188              echo "$violations"
189              echo "[ERROR] Found direct UUID usage! Use effects.gen_uuid() instead."
190              exit 1
191            else
192              echo "[OK] No direct UUID usage violations found"
193            fi
194  
195    check-documentation-links:
196      name: Check Documentation Links
197      runs-on: ubuntu-latest
198      steps:
199        - uses: actions/checkout@v4
200        - name: Check markdown links
201          uses: gaurav-nelson/github-action-markdown-link-check@v1
202          with:
203            use-quiet-mode: "yes"
204            use-verbose-mode: "yes"
205            config-file: ".github/config/markdown-link-check.json"
206            base-branch: "main"
207  
208    build:
209      name: Build Check
210      runs-on: ubuntu-latest
211      steps:
212        - uses: actions/checkout@v4
213        - name: Install Rust
214          uses: actions-rust-lang/setup-rust-toolchain@v1
215        - name: Cache cargo registry
216          uses: actions/cache@v3
217          with:
218            path: ~/.cargo/registry
219            key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
220        - name: Cache cargo index
221          uses: actions/cache@v3
222          with:
223            path: ~/.cargo/git
224            key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
225        - name: Cache cargo build
226          uses: actions/cache@v3
227          with:
228            path: target
229            key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
230        - name: Build workspace
231          run: cargo build --workspace --verbose
232  
233    unused-dependencies:
234      name: Check Unused Dependencies
235      runs-on: ubuntu-latest
236      continue-on-error: true  # Warning only - don't fail CI
237      steps:
238        - uses: actions/checkout@v4
239        - name: Install Rust nightly
240          uses: actions-rust-lang/setup-rust-toolchain@v1
241          with:
242            toolchain: nightly
243        - name: Install cargo-udeps
244          run: cargo install cargo-udeps --locked
245        - name: Cache cargo registry
246          uses: actions/cache@v3
247          with:
248            path: ~/.cargo/registry
249            key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
250        - name: Cache cargo index
251          uses: actions/cache@v3
252          with:
253            path: ~/.cargo/git
254            key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
255        - name: Check for unused dependencies
256          run: |
257            echo "Checking for unused dependencies..."
258            echo "Note: This job is advisory only and won't fail CI"
259            echo ""
260            cargo +nightly udeps --all-targets 2>&1 || true
261  
262    lint-test:
263      name: Verify Lint Enforcement Works
264      runs-on: ubuntu-latest
265      steps:
266        - uses: actions/checkout@v4
267        - name: Install Rust
268          uses: actions-rust-lang/setup-rust-toolchain@v1
269          with:
270            components: clippy
271        - name: Test lint enforcement (should fail)
272          run: |
273            echo "Testing that our lints catch violations..."
274            # This should fail due to linting violations
275            if cargo check --manifest-path=<(cat << 'EOF'
276            [package]
277            name = "lint-test"
278            version = "0.1.0"
279            edition = "2021"
280  
281            [lints]
282            workspace = true
283  
284            [dependencies]
285            uuid = "1.0"
286            rand = "0.8"
287  
288            [[bin]]
289            name = "test"
290            path = "test_lints.rs"
291            EOF
292            ); then
293              echo "[ERROR] Lints should have failed but didn't!"
294              exit 1
295            else
296              echo "[OK] Lints correctly caught violations"
297            fi
298  
299    lean-proofs:
300      name: Lean Formal Verification
301      runs-on: ubuntu-latest
302      # Only run when Lean files or kernel code changes
303      if: |
304        github.event_name == 'push' ||
305        contains(github.event.pull_request.labels.*.name, 'verification') ||
306        contains(join(github.event.pull_request.changed_files, ' '), 'verification/lean') ||
307        contains(join(github.event.pull_request.changed_files, ' '), 'crates/aura-journal') ||
308        contains(join(github.event.pull_request.changed_files, ' '), 'crates/aura-core/src/time')
309      steps:
310        - name: Free disk space
311          run: |
312            echo "Disk space before cleanup:"
313            df -h /
314            # Aggressive cleanup to ensure enough space for Nix store + cache
315            sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL
316            sudo rm -rf /usr/local/share/boost
317            sudo rm -rf /usr/share/swift
318            sudo rm -rf /opt/hostedtoolcache/Python /opt/hostedtoolcache/Ruby /opt/hostedtoolcache/go /opt/hostedtoolcache/node
319            sudo docker image prune --all --force || true
320            sudo apt-get clean || true
321            echo "Disk space after cleanup:"
322            df -h /
323        - uses: actions/checkout@v4
324        - name: Install Nix
325          uses: cachix/install-nix-action@v27
326          with:
327            nix_path: nixpkgs=channel:nixos-unstable
328        - name: Cache Nix store
329          uses: actions/cache@v3
330          with:
331            path: /nix/store
332            key: ${{ runner.os }}-nix-lean-${{ hashFiles('flake.lock') }}
333            restore-keys: |
334              ${{ runner.os }}-nix-lean-
335              ${{ runner.os }}-nix-
336        - name: Build Lean proofs
337          run: |
338            echo "Building Lean formal verification modules..."
339            nix develop --command bash -c "cd verification/lean && lake build"
340        - name: Check for incomplete proofs
341          run: |
342            echo "Checking for incomplete proofs (sorry usage)..."
343            if grep -r "sorry" verification/lean/Aura --include="*.lean" 2>/dev/null; then
344              echo "[WARNING] Found incomplete proofs (sorry). These should be resolved."
345              # Don't fail CI for now, just warn
346            else
347              echo "[OK] All proofs complete (no sorry found)"
348            fi
349        - name: Cleanup before cache save
350          if: always()
351          run: |
352            # Run nix garbage collection to reduce store size
353            nix-collect-garbage -d || true
354            echo "Disk space before cache save:"
355            df -h /
356  
357    differential-testing:
358      name: Lean Differential Testing
359      runs-on: ubuntu-latest
360      needs: [lean-proofs]
361      # Only run when Lean or kernel code changes
362      if: |
363        github.event_name == 'push' ||
364        contains(github.event.pull_request.labels.*.name, 'verification') ||
365        contains(join(github.event.pull_request.changed_files, ' '), 'verification/lean') ||
366        contains(join(github.event.pull_request.changed_files, ' '), 'crates/aura-journal') ||
367        contains(join(github.event.pull_request.changed_files, ' '), 'crates/aura-core/src/time') ||
368        contains(join(github.event.pull_request.changed_files, ' '), 'crates/aura-testkit')
369      steps:
370        - name: Free disk space
371          run: |
372            echo "Disk space before cleanup:"
373            df -h /
374            # Aggressive cleanup to ensure enough space for Nix store + cache
375            sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL
376            sudo rm -rf /usr/local/share/boost
377            sudo rm -rf /usr/share/swift
378            sudo rm -rf /opt/hostedtoolcache/Python /opt/hostedtoolcache/Ruby /opt/hostedtoolcache/go /opt/hostedtoolcache/node
379            sudo docker image prune --all --force || true
380            sudo apt-get clean || true
381            echo "Disk space after cleanup:"
382            df -h /
383        - uses: actions/checkout@v4
384        - name: Install Nix
385          uses: cachix/install-nix-action@v27
386          with:
387            nix_path: nixpkgs=channel:nixos-unstable
388        - name: Cache Nix store
389          uses: actions/cache@v3
390          with:
391            path: /nix/store
392            key: ${{ runner.os }}-nix-differential-${{ hashFiles('flake.lock') }}
393            restore-keys: |
394              ${{ runner.os }}-nix-differential-
395              ${{ runner.os }}-nix-
396        - name: Cache cargo registry
397          uses: actions/cache@v3
398          with:
399            path: ~/.cargo/registry
400            key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
401        - name: Cache cargo build
402          uses: actions/cache@v3
403          with:
404            path: target
405            key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
406        - name: Build Lean oracle
407          run: |
408            echo "Building Lean oracle verifier..."
409            nix develop --command just lean-oracle-build
410        - name: Run differential tests
411          run: |
412            echo "Running differential tests (Rust vs Lean oracle)..."
413            nix develop --command just test-differential
414        - name: Cleanup before cache save
415          if: always()
416          run: |
417            # Clean up build artifacts before cache save to reduce cache size
418            rm -rf target/debug/deps target/debug/build target/debug/incremental || true
419            # Run nix garbage collection to reduce store size
420            nix-collect-garbage -d || true
421            echo "Disk space before cache save:"
422            df -h /
423  
424    quint-model-checking:
425      name: Quint Model Checking
426      runs-on: ubuntu-latest
427      # Only run when Quint specs or consensus code changes
428      if: |
429        github.event_name == 'push' ||
430        contains(github.event.pull_request.labels.*.name, 'verification') ||
431        contains(join(github.event.pull_request.changed_files, ' '), 'verification/quint') ||
432        contains(join(github.event.pull_request.changed_files, ' '), 'crates/aura-protocol/src/consensus')
433      steps:
434        - uses: actions/checkout@v4
435        - name: Setup Node.js
436          uses: actions/setup-node@v4
437          with:
438            node-version: '20'
439        - name: Install Quint
440          run: npm install -g @informalsystems/quint@0.25.1
441        - name: Typecheck Quint specs
442          run: |
443            echo "Typechecking all Quint specifications..."
444            cd verification/quint
445            for spec in *.qnt; do
446              echo "  Checking $spec..."
447              quint typecheck "$spec" || exit 1
448            done
449            echo "[OK] All Quint specs typecheck"
450        - name: Run Quint invariant verification
451          run: |
452            echo "Running Quint model checking on consensus specs..."
453            cd verification/quint
454  
455            # Verify core consensus invariants
456            echo "  Verifying consensus/core.qnt..."
457            quint verify --invariant=AllInvariants consensus/core.qnt --max-samples=1000 || exit 1
458  
459            # Verify adversary model
460            echo "  Verifying consensus/adversary.qnt..."
461            quint verify --invariant=InvariantByzantineThreshold consensus/adversary.qnt --max-samples=500 || exit 1
462  
463            # Verify liveness properties
464            echo "  Verifying consensus/liveness.qnt..."
465            quint verify --invariant=InvariantProgressUnderSynchrony consensus/liveness.qnt --max-samples=500 || exit 1
466  
467            echo "[OK] All Quint invariants pass model checking"
468  
469    kani-verification:
470      name: Kani Bounded Model Checking
471      runs-on: ubuntu-latest
472      # Only run on push to main or when explicitly requested via label
473      # Kani takes significant time (~30+ minutes) so we don't run on every PR
474      if: |
475        github.event_name == 'push' && github.ref == 'refs/heads/main' ||
476        contains(github.event.pull_request.labels.*.name, 'kani')
477      steps:
478        - name: Free disk space
479          run: |
480            echo "Disk space before cleanup:"
481            df -h /
482            sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL
483            sudo rm -rf /usr/local/share/boost /usr/share/swift
484            sudo rm -rf /opt/hostedtoolcache/Python /opt/hostedtoolcache/Ruby /opt/hostedtoolcache/go /opt/hostedtoolcache/node
485            sudo docker image prune --all --force || true
486            sudo apt-get clean || true
487            echo "Disk space after cleanup:"
488            df -h /
489        - uses: actions/checkout@v4
490        - name: Install Rust nightly
491          uses: actions-rust-lang/setup-rust-toolchain@v1
492          with:
493            toolchain: nightly
494            components: rust-src
495        - name: Install CBMC
496          run: |
497            sudo apt-get update
498            sudo apt-get install -y cbmc
499        - name: Cache Kani installation
500          uses: actions/cache@v3
501          with:
502            path: ~/.cargo/bin/cargo-kani
503            key: ${{ runner.os }}-kani-0.56.0
504        - name: Install Kani
505          run: |
506            if ! command -v cargo-kani &> /dev/null; then
507              echo "Installing Kani verifier..."
508              cargo install --locked kani-verifier
509              cargo kani setup
510            else
511              echo "Kani already installed"
512            fi
513        - name: Cache cargo registry
514          uses: actions/cache@v3
515          with:
516            path: ~/.cargo/registry
517            key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
518        - name: Cache cargo build
519          uses: actions/cache@v3
520          with:
521            path: target
522            key: ${{ runner.os }}-cargo-build-kani-${{ hashFiles('**/Cargo.lock') }}
523        - name: Run Kani proofs
524          run: |
525            echo "Running Kani bounded model checking on consensus core..."
526            echo "This verifies:"
527            echo "  - Invariant preservation (apply_share, trigger_fallback, fail_consensus)"
528            echo "  - Monotonicity (proposals never shrink, equivocators never removed)"
529            echo "  - Panic freedom (no panics on valid inputs)"
530            echo "  - Phase transitions (terminal states stable)"
531            echo "  - Agreement (commit matches threshold result)"
532            echo "  - Reference equivalence (production matches reference)"
533            echo ""
534  
535            # Run each harness with a timeout
536            # Using --default-unwind to limit loop unwinding
537            cargo kani --package aura-protocol \
538              --default-unwind 10 \
539              --output-format terse \
540              2>&1 | tee kani-output.log
541  
542            echo ""
543            echo "========================================"
544            echo "KANI VERIFICATION COMPLETE"
545            echo "========================================"
546        - name: Upload Kani results
547          if: always()
548          uses: actions/upload-artifact@v3
549          with:
550            name: kani-verification-results
551            path: kani-output.log
552  
553    consensus-conformance:
554      name: Consensus Conformance Tests
555      runs-on: ubuntu-latest
556      needs: [test]
557      steps:
558        - uses: actions/checkout@v4
559        - name: Install Rust
560          uses: actions-rust-lang/setup-rust-toolchain@v1
561        - name: Setup Node.js
562          uses: actions/setup-node@v4
563          with:
564            node-version: '20'
565        - name: Install Quint
566          run: npm install -g @informalsystems/quint@0.25.1
567        - name: Cache cargo registry
568          uses: actions/cache@v3
569          with:
570            path: ~/.cargo/registry
571            key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
572        - name: Cache cargo build
573          uses: actions/cache@v3
574          with:
575            path: target
576            key: ${{ runner.os }}-cargo-build-conformance-${{ hashFiles('**/Cargo.lock') }}
577        - name: Generate fresh ITF traces
578          run: |
579            echo "Generating ITF traces from Quint spec..."
580            # Generate traces to the dedicated traces directory
581            quint run --out-itf=traces/consensus.itf.json verification/quint/consensus/core.qnt --max-steps=30 --max-samples=5
582            echo "[OK] Generated fresh ITF traces to traces/"
583        - name: Run ITF conformance tests
584          run: |
585            echo "Running ITF conformance tests..."
586            cargo test -p aura-testkit --test consensus_itf_conformance -- --nocapture
587        - name: Run differential tests
588          run: |
589            echo "Running differential tests (production vs reference)..."
590            cargo test -p aura-testkit --test consensus_differential -- --nocapture
591        - name: Report conformance status
592          if: always()
593          run: |
594            echo "========================================"
595            echo "CONSENSUS CONFORMANCE REPORT"
596            echo "========================================"
597            echo "ITF Conformance: Tests verify Rust pure core matches Quint spec"
598            echo "Differential: Tests verify production matches reference model"
599            echo "========================================"