Merge pull request #400 from ahrav/feature/postgres-git-persistence-b… #1700
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: | |
| push: | |
| branches: [main] | |
| paths-ignore: | |
| - '.beads/**' | |
| - 'docs/**' | |
| - 'diagrams/**' | |
| - '*.md' | |
| - 'LICENSE' | |
| pull_request: | |
| branches: [main] | |
| paths-ignore: | |
| - '.beads/**' | |
| - 'docs/**' | |
| - 'diagrams/**' | |
| - '*.md' | |
| - 'LICENSE' | |
| schedule: | |
| - cron: "0 2 * * *" # nightly at 02:00 UTC | |
| workflow_dispatch: | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} | |
| permissions: | |
| contents: read | |
| actions: read | |
| env: | |
| CARGO_TERM_COLOR: always | |
| CARGO_INCREMENTAL: "0" | |
| CARGO_PROFILE_DEV_DEBUG: "0" | |
| CARGO_PROFILE_TEST_DEBUG: "0" | |
| SCCACHE_GHA_ENABLED: "true" | |
| RUSTC_WRAPPER: "sccache" | |
| jobs: | |
| # ── Path filtering — skip expensive jobs on docs-only PRs ────────── | |
| changes: | |
| name: Detect Changes | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| if: github.event_name == 'pull_request' | |
| outputs: | |
| rust: ${{ steps.filter.outputs.rust }} | |
| fuzz: ${{ steps.filter.outputs.fuzz }} | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 | |
| id: filter | |
| with: | |
| filters: | | |
| rust: | |
| - 'crates/**' | |
| - 'tests/**' | |
| - 'benches/**' | |
| - 'examples/**' | |
| - 'tools/**' | |
| - 'Cargo.toml' | |
| - 'Cargo.lock' | |
| - '.github/**' | |
| fuzz: | |
| - 'crates/scanner-git/fuzz/**' | |
| - 'crates/scanner-git/src/**' | |
| # ── Format ───────────────────────────────────────────────────────── | |
| fmt: | |
| name: Format | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| if: ${{ always() && !cancelled() && (github.event_name != 'pull_request' || needs.changes.outputs.rust == 'true') }} | |
| needs: [changes] | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # master | |
| with: | |
| toolchain: stable | |
| components: rustfmt | |
| # No cache needed — fmt does not compile | |
| - run: cargo fmt --all -- --check | |
| # ── Lint ─────────────────────────────────────────────────────────── | |
| clippy: | |
| name: Clippy (${{ matrix.label }}) | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 20 | |
| if: ${{ always() && !cancelled() && (github.event_name != 'pull_request' || needs.changes.outputs.rust == 'true') }} | |
| needs: [changes] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-24.04 | |
| shared-key: stable-linux | |
| label: x86_64 | |
| - os: macos-15 | |
| shared-key: stable-macos | |
| label: aarch64 | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - uses: ./.github/actions/setup-rust | |
| with: | |
| toolchain: stable | |
| components: clippy | |
| shared-key: ${{ matrix.shared-key }} | |
| - run: cargo clippy --all-targets --all-features -- -D warnings | |
| # ── Test ─────────────────────────────────────────────────────────── | |
| # PR/push: lightweight feature set — excludes stdx-proptest, sim-harness, | |
| # tiger-harness, kgram-gate, bench, b64-stats, perf-counters. | |
| # Those are exercised nightly by test-full-harness with --all-features. | |
| # Infrastructure-dependent crates (postgres, etcd) run in dedicated jobs | |
| # with service containers. | |
| test: | |
| name: Test (${{ matrix.label }}) | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: ${{ matrix.timeout }} | |
| if: ${{ always() && !cancelled() && (github.event_name != 'pull_request' || needs.changes.outputs.rust == 'true') }} | |
| needs: [changes] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-24.04 | |
| shared-key: stable-linux | |
| timeout: 15 | |
| label: x86_64 | |
| extra-excludes: "" | |
| - os: macos-15 | |
| shared-key: stable-macos | |
| timeout: 30 | |
| label: aarch64 | |
| # gossip-worker tests need Docker (testcontainers) which is | |
| # unavailable on macOS runners. Those tests run on x86_64/ubuntu | |
| # and in the dedicated postgres/etcd CI jobs. | |
| extra-excludes: "--exclude gossip-worker" | |
| env: | |
| PROPTEST_CASES: "4" | |
| STRESS_ITERATIONS: "20" | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - uses: ./.github/actions/setup-rust | |
| with: | |
| toolchain: stable | |
| shared-key: ${{ matrix.shared-key }} | |
| save-cache: "false" | |
| - name: Install cargo-nextest | |
| uses: taiki-e/install-action@06203676c62f0d3c765be3f2fcfbebbcb02d09f5 # v2.69.6 | |
| with: | |
| tool: cargo-nextest | |
| - name: Build tests | |
| run: >- | |
| cargo nextest run | |
| --workspace | |
| --features test-support,perf-stats | |
| --exclude gossip-findings-postgres | |
| --exclude gossip-done-ledger-postgres | |
| --exclude gossip-git-persistence-postgres | |
| --exclude gossip-coordination-etcd | |
| ${{ matrix.extra-excludes }} | |
| --no-run | |
| --profile ci | |
| - name: Run tests | |
| run: >- | |
| cargo nextest run | |
| --workspace | |
| --features test-support,perf-stats | |
| --exclude gossip-findings-postgres | |
| --exclude gossip-done-ledger-postgres | |
| --exclude gossip-git-persistence-postgres | |
| --exclude gossip-coordination-etcd | |
| ${{ matrix.extra-excludes }} | |
| --profile ci | |
| # nextest does not run doctests; run them separately. | |
| - name: Run doctests | |
| run: >- | |
| cargo test | |
| --features test-support,perf-stats | |
| --doc | |
| # ── Test (PostgreSQL integration) ───────────────────────────────── | |
| # Runs PostgreSQL-dependent tests against a real service container. | |
| # GOSSIP_POSTGRES_TEST_URL bypasses testcontainers so all PG crates | |
| # share the same service container instead of spinning up their own. | |
| test-postgres: | |
| name: Test (PostgreSQL) | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 15 | |
| if: ${{ always() && !cancelled() && (github.event_name != 'pull_request' || needs.changes.outputs.rust == 'true') }} | |
| needs: [changes] | |
| services: | |
| postgres: | |
| image: postgres:16-alpine | |
| env: | |
| POSTGRES_USER: postgres | |
| POSTGRES_PASSWORD: postgres | |
| POSTGRES_DB: postgres | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 5432:5432 | |
| env: | |
| GOSSIP_POSTGRES_TEST_URL: "host=localhost port=5432 user=postgres password=postgres dbname=postgres" | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - uses: ./.github/actions/setup-rust | |
| with: | |
| toolchain: stable | |
| shared-key: stable-linux | |
| save-cache: "false" | |
| - name: Install cargo-nextest | |
| uses: taiki-e/install-action@06203676c62f0d3c765be3f2fcfbebbcb02d09f5 # v2.69.6 | |
| with: | |
| tool: cargo-nextest | |
| - name: Run PostgreSQL integration tests (done-ledger) | |
| run: >- | |
| cargo nextest run | |
| -p gossip-done-ledger-postgres | |
| --profile ci | |
| - name: Run PostgreSQL integration tests (findings) | |
| run: >- | |
| cargo nextest run | |
| -p gossip-findings-postgres | |
| --features test-utils | |
| --profile ci | |
| - name: Run PostgreSQL integration tests (git-persistence) | |
| run: >- | |
| cargo nextest run | |
| -p gossip-git-persistence-postgres | |
| --features test-utils | |
| --profile ci | |
| - name: Run PostgreSQL integration tests (persistence differential oracle) | |
| run: >- | |
| cargo nextest run | |
| -p gossip-persistence-inmemory | |
| --run-ignored ignored-only | |
| --profile ci | |
| -E 'test(done_ledger_pg_matches_in_memory)' | |
| # ── Test (etcd integration) ────────────────────────────────────── | |
| # Runs etcd-dependent tests against a real service container. | |
| # ETCD_ENDPOINTS bypasses testcontainers so all tests share the | |
| # same service container instead of spinning up their own. | |
| # | |
| # The coreos/etcd image is distroless (no /bin/sh), so Docker's | |
| # CMD-SHELL health checks cannot work. We skip the health check | |
| # in `options` and instead poll the HTTP /health endpoint from | |
| # the runner in an explicit step. | |
| test-etcd: | |
| name: Test (etcd) | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 15 | |
| if: ${{ always() && !cancelled() && (github.event_name != 'pull_request' || needs.changes.outputs.rust == 'true') }} | |
| needs: [changes] | |
| services: | |
| etcd: | |
| image: quay.io/coreos/etcd:v3.5.15 | |
| env: | |
| ETCD_NAME: node1 | |
| ETCD_DATA_DIR: /etcd-data | |
| ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379 | |
| ETCD_ADVERTISE_CLIENT_URLS: http://etcd:2379 | |
| ETCD_LISTEN_PEER_URLS: http://0.0.0.0:2380 | |
| ETCD_INITIAL_ADVERTISE_PEER_URLS: http://etcd:2380 | |
| ETCD_INITIAL_CLUSTER: node1=http://etcd:2380 | |
| ETCD_INITIAL_CLUSTER_TOKEN: gossip-rs-ci-cluster | |
| ETCD_INITIAL_CLUSTER_STATE: new | |
| ports: | |
| - 2379:2379 | |
| env: | |
| ETCD_ENDPOINTS: "http://localhost:2379" | |
| steps: | |
| - name: Wait for etcd | |
| run: | | |
| for i in $(seq 1 30); do | |
| if curl -sf http://localhost:2379/health > /dev/null 2>&1; then | |
| echo "etcd is ready" | |
| exit 0 | |
| fi | |
| echo "Waiting for etcd... ($i/30)" | |
| sleep 1 | |
| done | |
| echo "etcd failed to become ready" | |
| docker logs "$(docker ps -q --filter ancestor=quay.io/coreos/etcd:v3.5.15)" 2>&1 || true | |
| exit 1 | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - uses: ./.github/actions/setup-rust | |
| with: | |
| toolchain: stable | |
| shared-key: stable-linux | |
| save-cache: "false" | |
| - name: Install cargo-nextest | |
| uses: taiki-e/install-action@06203676c62f0d3c765be3f2fcfbebbcb02d09f5 # v2.69.6 | |
| with: | |
| tool: cargo-nextest | |
| - name: Run etcd integration tests | |
| run: >- | |
| cargo nextest run | |
| -p gossip-coordination-etcd | |
| --features test-support | |
| --profile ci | |
| - name: Run etcd ignored tests (differential oracle, needs service container) | |
| run: >- | |
| cargo nextest run | |
| -p gossip-coordination-etcd | |
| --features test-support | |
| --run-ignored ignored-only | |
| --profile ci | |
| -E 'binary(~differential_oracle)' | |
| - name: Run etcd integration tests (release) | |
| if: github.event_name == 'schedule' | |
| run: >- | |
| cargo nextest run | |
| -p gossip-coordination-etcd | |
| --features test-support | |
| --release | |
| --profile nightly | |
| - name: Run all etcd ignored tests (release, nightly) | |
| if: github.event_name == 'schedule' | |
| run: >- | |
| cargo nextest run | |
| -p gossip-coordination-etcd | |
| --features test-support | |
| --release | |
| --run-ignored ignored-only | |
| --profile nightly | |
| # ── Benchmark (compile check) ──────────────────────────────────── | |
| bench: | |
| name: Bench (compile) | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| if: ${{ always() && !cancelled() && (github.event_name != 'pull_request' || needs.changes.outputs.rust == 'true') }} | |
| needs: [changes] | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - uses: ./.github/actions/setup-rust | |
| with: | |
| toolchain: stable | |
| shared-key: stable-linux-bench | |
| save-cache: "true" | |
| - run: cargo bench --features test-support,bench,perf-stats --no-run | |
| # ── MSRV ────────────────────────────────────────────────────────── | |
| msrv: | |
| name: MSRV (1.93) | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 15 | |
| if: ${{ always() && !cancelled() && (github.event_name != 'pull_request' || needs.changes.outputs.rust == 'true') }} | |
| needs: [changes] | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - uses: ./.github/actions/setup-rust | |
| with: | |
| toolchain: "1.93" | |
| shared-key: msrv-linux | |
| - run: cargo check --all-features | |
| # ── Docs ─────────────────────────────────────────────────────────── | |
| doc: | |
| name: Doc | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| if: ${{ always() && !cancelled() && (github.event_name != 'pull_request' || needs.changes.outputs.rust == 'true') }} | |
| needs: [changes] | |
| env: | |
| RUSTDOCFLAGS: -D warnings | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - uses: ./.github/actions/setup-rust | |
| with: | |
| toolchain: stable | |
| shared-key: stable-linux-doc | |
| save-cache: "true" | |
| # Explicit feature list instead of --all-features: avoids compiling | |
| # librocksdb-sys (C++ from source, ~4 min on CI) which adds no public | |
| # API surface. Only test-support features are needed to resolve | |
| # doc-links to cfg-gated modules. | |
| - run: >- | |
| cargo doc --no-deps | |
| --features gossip-contracts/test-support,gossip-coordination/test-support,gossip-coordination-etcd/test-support,gossip-git-persistence-postgres/test-utils,gossip-pg-common/test-support,gossip-done-ledger-postgres/test-utils,gossip-findings-postgres/test-utils,gossip-persistence-inmemory/test-support,gossip-scanner-runtime/test-support,scanner-engine/test-support | |
| # ── Dependency Lint (licenses, advisories, bans) ─────────────────── | |
| deny: | |
| name: cargo-deny | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| if: ${{ always() && !cancelled() && (github.event_name != 'pull_request' || needs.changes.outputs.rust == 'true') }} | |
| needs: [changes] | |
| env: | |
| RUSTC_WRAPPER: "" | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - uses: EmbarkStudios/cargo-deny-action@3fd3802e88374d3fe9159b834c7714ec57d6c979 # v2 | |
| with: | |
| command: check | |
| arguments: --all-features | |
| # ── Eval Harness ────────────────────────────────────────────────── | |
| # Separate workspace in tools/eval-harness/ — has its own Cargo.toml | |
| # and Cargo.lock, so fmt/clippy/test run independently. | |
| eval-harness: | |
| name: Eval Harness | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 15 | |
| if: ${{ always() && !cancelled() && (github.event_name != 'pull_request' || needs.changes.outputs.rust == 'true') }} | |
| needs: [changes] | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - uses: ./.github/actions/setup-rust | |
| with: | |
| toolchain: stable | |
| components: rustfmt, clippy | |
| shared-key: eval-harness-linux | |
| cache-workspaces: "tools/eval-harness -> target" | |
| - run: cargo fmt --check | |
| working-directory: tools/eval-harness | |
| - run: cargo clippy --all-targets --all-features -- -D warnings | |
| working-directory: tools/eval-harness | |
| - name: Install cargo-nextest | |
| uses: taiki-e/install-action@06203676c62f0d3c765be3f2fcfbebbcb02d09f5 # v2.69.6 | |
| with: | |
| tool: cargo-nextest | |
| - run: cargo nextest run | |
| working-directory: tools/eval-harness | |
| # ── Dependency Review (PR only) ──────────────────────────────────── | |
| # Catches newly introduced vulnerable or license-incompatible deps at | |
| # PR time, before they land. Complements cargo-deny (license/ban rules) | |
| # and rustsec/audit-check (post-merge advisory scan). | |
| dependency-review: | |
| name: Dependency Review | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| if: github.event_name == 'pull_request' | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0 | |
| with: | |
| fail-on-severity: high | |
| deny-licenses: AGPL-3.0, AGPL-3.0-only, AGPL-3.0-or-later, GPL-3.0, GPL-3.0-only, GPL-3.0-or-later | |
| # ── Security Audit ───────────────────────────────────────────────── | |
| audit: | |
| name: Security Audit | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| env: | |
| RUSTC_WRAPPER: "" | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - uses: rustsec/audit-check@69366f33c96575abad1ee0dba8212993eecbe998 # v2.0.0 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| # ── Fuzz Pack Smoke (PR only) ─────────────────────────────────────── | |
| # Quick sanity check on pack-format fuzz targets when fuzz or source | |
| # files change in a PR. Each target runs for 20 seconds max. | |
| # Targets are auto-discovered from fuzz_targets/fuzz_pack_*.rs. | |
| fuzz-pack-smoke: | |
| name: Fuzz Pack Smoke (PR) | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| if: github.event_name == 'pull_request' && needs.changes.outputs.fuzz == 'true' | |
| needs: [changes] | |
| env: | |
| RUSTC_WRAPPER: "" | |
| FUZZ_SECONDS: 20 | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - uses: ./.github/actions/setup-rust | |
| with: | |
| toolchain: nightly | |
| components: rust-src | |
| shared-key: nightly-linux-fuzz-smoke | |
| extra-apt: "clang llvm" | |
| cache-workspaces: "crates/scanner-git/fuzz -> target" | |
| - uses: ./.github/actions/setup-cargo-fuzz | |
| - name: Restore fuzz corpus cache | |
| uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 | |
| with: | |
| path: crates/scanner-git/fuzz/corpus | |
| key: fuzz-corpus-pack-${{ github.sha }} | |
| restore-keys: | | |
| fuzz-corpus-pack- | |
| - name: Run pack fuzz targets | |
| working-directory: crates/scanner-git/fuzz | |
| run: | | |
| set -euo pipefail | |
| ran=0 | |
| for f in fuzz_targets/fuzz_pack_*.rs; do | |
| [ -f "$f" ] || continue | |
| target="$(basename "${f%.rs}")" | |
| echo "Running: ${target}" | |
| cargo +nightly fuzz run "${target}" \ | |
| --target x86_64-unknown-linux-gnu \ | |
| -- -max_total_time="${FUZZ_SECONDS}" | |
| ran=$((ran + 1)) | |
| done | |
| if [ "${ran}" -eq 0 ]; then | |
| echo "::error::No fuzz targets discovered — expected fuzz_targets/fuzz_pack_*.rs" | |
| exit 1 | |
| fi | |
| echo "Ran ${ran} fuzz target(s)" | |
| # ══════════════════════════════════════════════════════════════════ | |
| # Scheduled-only jobs (nightly at 02:00 UTC) | |
| # ══════════════════════════════════════════════════════════════════ | |
| # ── Test (full harness, nightly) ───────────────────────────────── | |
| # --all-features enables stdx-proptest, sim-harness, tiger-harness, | |
| # kgram-gate, b64-stats, perf-counters — everything the PR job skips. | |
| # Debug and release run as parallel matrix legs for faster wall-clock time. | |
| test-full-harness: | |
| name: Test (Scheduled, ${{ matrix.profile }}) | |
| if: github.event_name == 'schedule' | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 45 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - profile: debug | |
| release-flag: "" | |
| - profile: release | |
| release-flag: "--release" | |
| env: | |
| PROPTEST_CASES: "64" | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - uses: ./.github/actions/setup-rust | |
| with: | |
| toolchain: stable | |
| shared-key: stable-linux | |
| save-cache: "false" | |
| - name: Install cargo-nextest | |
| uses: taiki-e/install-action@06203676c62f0d3c765be3f2fcfbebbcb02d09f5 # v2.69.6 | |
| with: | |
| tool: cargo-nextest | |
| - name: Run tests (${{ matrix.profile }}) | |
| run: >- | |
| cargo nextest run --workspace --all-features ${{ matrix.release-flag }} | |
| --profile nightly | |
| --exclude gossip-coordination-etcd | |
| - name: Run ignored slow tests (${{ matrix.profile }}) | |
| run: >- | |
| cargo nextest run --workspace --all-features ${{ matrix.release-flag }} | |
| --profile nightly | |
| --exclude gossip-coordination-etcd | |
| --run-ignored ignored-only | |
| - name: Run doctests (${{ matrix.profile }}) | |
| run: >- | |
| cargo test --all-features ${{ matrix.release-flag }} --doc | |
| # ── Miri (nightly only) ──────────────────────────────────────────── | |
| miri: | |
| name: Miri (Scheduled, Nightly) | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 180 | |
| if: >- | |
| github.event_name == 'schedule' || | |
| github.event_name == 'workflow_dispatch' | |
| env: | |
| RUSTC_WRAPPER: "" | |
| MIRIFLAGS: >- | |
| -Zmiri-strict-provenance | |
| -Zmiri-symbolic-alignment-check | |
| -Zmiri-preemption-rate=0.1 | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - uses: ./.github/actions/setup-rust | |
| with: | |
| toolchain: nightly | |
| components: miri | |
| shared-key: nightly-linux-miri | |
| # Pure-Rust crates — run full test suites under Miri. | |
| - name: Miri — gossip-stdx | |
| run: cargo +nightly miri test -p gossip-stdx --all-features | |
| - name: Miri — gossip-contracts | |
| run: cargo +nightly miri test -p gossip-contracts --all-features | |
| - name: Miri — gossip-coordination | |
| run: cargo +nightly miri test -p gossip-coordination --all-features | |
| - name: Miri — gossip-frontier | |
| run: cargo +nightly miri test -p gossip-frontier --all-features | |
| # FFI-heavy crates — Miri interprets Rust but cannot execute C/asm. | |
| # Most scanner-engine tests call Vectorscan FFI (via demo_engine / | |
| # cached_demo_engine → VsPrefilterDb::try_new → hs_compile_multi), | |
| # which Miri cannot interpret. Only run modules with pure-Rust logic | |
| # and #[cfg(miri)] scalar fallbacks: simd_classify, offline_validate, | |
| # and set_associative_cache. Use aegis-pure-rust to avoid AES-NI / | |
| # NEON intrinsics that Miri cannot interpret. | |
| - name: Miri — scanner-engine (pure-Rust paths) | |
| run: >- | |
| cargo +nightly miri test -p scanner-engine | |
| --features aegis-pure-rust,test-support | |
| -- simd_classify offline_validate set_associative_cache | |
| # scanner-git: all tests call Vectorscan FFI (via | |
| # demo_engine_with_anchor_mode); no miri-safe test modules exist yet. | |
| # TODO: add #[cfg(not(miri))] guards to engine_adapter_tests and | |
| # introduce miri-safe unit tests for pure-Rust logic. | |
| # ── Kani (nightly only) ────────────────────────────────────────────── | |
| kani: | |
| name: Kani Verification (Scheduled) | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 180 | |
| if: >- | |
| github.event_name == 'schedule' || | |
| github.event_name == 'workflow_dispatch' | |
| env: | |
| RUSTC_WRAPPER: "" | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| # Kani verifies all #[kani::proof] harnesses per crate. | |
| # 100 total proofs across 4 crates. | |
| - name: Kani — gossip-stdx (74 proofs) | |
| uses: model-checking/kani-github-action@f838096619a707b0f6b2118cf435eaccfa33e51f # v1.1 | |
| with: | |
| args: --package gossip-stdx --features kani | |
| - name: Kani — scanner-engine (15 proofs) | |
| uses: model-checking/kani-github-action@f838096619a707b0f6b2118cf435eaccfa33e51f # v1.1 | |
| with: | |
| args: --package scanner-engine --features kani | |
| - name: Kani — scanner-scheduler (9 proofs) | |
| uses: model-checking/kani-github-action@f838096619a707b0f6b2118cf435eaccfa33e51f # v1.1 | |
| with: | |
| args: --package scanner-scheduler --features kani | |
| - name: Kani — scanner-git (2 proofs) | |
| uses: model-checking/kani-github-action@f838096619a707b0f6b2118cf435eaccfa33e51f # v1.1 | |
| with: | |
| args: --package scanner-git --features kani | |
| # ── Loom (concurrency model checking) ──────────────────────────────── | |
| loom: | |
| name: Loom Concurrency (Scheduled) | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 60 | |
| if: >- | |
| github.event_name == 'schedule' || | |
| github.event_name == 'workflow_dispatch' | |
| env: | |
| RUSTC_WRAPPER: "" | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - uses: ./.github/actions/setup-rust | |
| with: | |
| toolchain: stable | |
| shared-key: stable-linux | |
| save-cache: "false" | |
| # Loom exhaustively explores thread interleavings. | |
| - name: Loom — gossip-stdx (11 tests) | |
| run: >- | |
| RUSTFLAGS="--cfg loom" cargo test --lib | |
| -p gossip-stdx --features loom -- loom_tests | |
| - name: Loom — scanner-scheduler (4 tests) | |
| run: >- | |
| RUSTFLAGS="--cfg loom" cargo test --lib | |
| -p scanner-scheduler --features loom -- loom_tests | |
| # ── ASAN (nightly only) ───────────────────────────────────────────── | |
| asan: | |
| name: ASAN (Scheduled, Nightly) | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 60 | |
| if: >- | |
| github.event_name == 'schedule' || | |
| github.event_name == 'workflow_dispatch' | |
| env: | |
| RUSTC_WRAPPER: "" | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - uses: ./.github/actions/setup-rust | |
| with: | |
| toolchain: nightly | |
| components: rust-src | |
| shared-key: nightly-linux-asan | |
| # AddressSanitizer targets SIMD and offline validation modules in | |
| # scanner-engine where unsafe pointer arithmetic is concentrated. | |
| - name: ASAN — scanner-engine (simd_classify + offline_validate) | |
| run: >- | |
| RUSTFLAGS="-Zsanitizer=address" cargo +nightly test | |
| -Zbuild-std --target x86_64-unknown-linux-gnu | |
| -p scanner-engine --lib | |
| -- engine::simd_classify engine::offline_validate | |
| # ── Fuzz (nightly) ────────────────────────────────────────────────── | |
| # Discovers and runs all fuzz targets across every crate with a fuzz/ | |
| # directory. Each target gets 10K iterations per platform. | |
| # | |
| # On Linux we install clang+llvm via extra-apt because the system GCC | |
| # lacks sanitizer integration. On macOS, Xcode ships Apple Clang with | |
| # full libFuzzer and AddressSanitizer support, so no extra packages. | |
| fuzz-nightly: | |
| name: Fuzz (Scheduled, ${{ matrix.label }}) | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 90 | |
| if: github.event_name == 'schedule' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-24.04 | |
| target: x86_64-unknown-linux-gnu | |
| label: Linux | |
| shared-key: nightly-linux-fuzz | |
| extra-apt: "clang llvm" | |
| - os: macos-15 | |
| target: aarch64-apple-darwin | |
| label: macOS | |
| shared-key: nightly-macos-fuzz | |
| env: | |
| RUSTC_WRAPPER: "" | |
| FUZZ_RUNS: 10000 | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - uses: ./.github/actions/setup-rust | |
| with: | |
| toolchain: nightly | |
| components: rust-src | |
| shared-key: ${{ matrix.shared-key }} | |
| extra-apt: ${{ matrix.extra-apt }} | |
| - uses: ./.github/actions/setup-cargo-fuzz | |
| # Corpus accumulates: restore latest via prefix, save under unique key. | |
| # GitHub's 10 GB LRU cache evicts oldest entries automatically. | |
| - name: Restore fuzz corpus cache | |
| uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 | |
| with: | |
| path: | | |
| crates/gossip-contracts/fuzz/corpus | |
| crates/gossip-coordination-etcd/fuzz/corpus | |
| crates/gossip-stdx/fuzz/corpus | |
| crates/scanner-engine/fuzz/corpus | |
| crates/scanner-git/fuzz/corpus | |
| key: fuzz-corpus-nightly-${{ matrix.label }}-${{ github.run_number }} | |
| restore-keys: | | |
| fuzz-corpus-nightly-${{ matrix.label }}- | |
| - name: Fuzz all targets | |
| env: | |
| FUZZ_TARGET_TRIPLE: ${{ matrix.target }} | |
| run: | | |
| set -euo pipefail | |
| failed=0 | |
| total_ran=0 | |
| for fuzz_dir in crates/*/fuzz; do | |
| [ -d "${fuzz_dir}/fuzz_targets" ] || continue | |
| crate_name="$(basename "$(dirname "${fuzz_dir}")")" | |
| targets=() | |
| for f in "${fuzz_dir}"/fuzz_targets/fuzz_*.rs; do | |
| [ -f "$f" ] || continue | |
| targets+=("$(basename "${f%.rs}")") | |
| done | |
| [ "${#targets[@]}" -gt 0 ] || continue | |
| echo "::group::${crate_name} (${#targets[@]} targets)" | |
| cd "${fuzz_dir}" | |
| for target in "${targets[@]}"; do | |
| echo " Running: ${target}" | |
| if ! cargo +nightly fuzz run "${target}" \ | |
| --target "${FUZZ_TARGET_TRIPLE}" \ | |
| -- -runs="${FUZZ_RUNS}"; then | |
| echo "::error::Fuzz target ${target} in ${crate_name} failed" | |
| failed=1 | |
| fi | |
| total_ran=$((total_ran + 1)) | |
| done | |
| cd "${GITHUB_WORKSPACE}" | |
| echo "::endgroup::" | |
| done | |
| if [ "${total_ran}" -eq 0 ]; then | |
| echo "::error::No fuzz targets discovered across any crate" | |
| exit 1 | |
| fi | |
| echo "Ran ${total_ran} fuzz target(s) total" | |
| [ "${failed}" -eq 0 ] || exit 1 | |
| # ── Nightly Summary ─────────────────────────────────────────────── | |
| nightly-summary: | |
| name: Nightly Summary | |
| if: always() && github.event_name == 'schedule' | |
| needs: [test-full-harness, test-etcd, test-postgres, miri, kani, loom, asan, fuzz-nightly, audit] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| permissions: | |
| issues: write | |
| steps: | |
| - name: Create issue on failure | |
| if: contains(needs.*.result, 'failure') | |
| uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | |
| with: | |
| script: | | |
| const { owner, repo } = context.repo; | |
| const runUrl = `${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`; | |
| const today = new Date().toISOString().split('T')[0]; | |
| const existing = await github.rest.issues.listForRepo({ | |
| owner, repo, | |
| labels: 'nightly-failure', | |
| state: 'open', | |
| per_page: 1, | |
| sort: 'created', | |
| direction: 'desc', | |
| }); | |
| if (existing.data.length > 0) { | |
| await github.rest.issues.createComment({ | |
| owner, repo, | |
| issue_number: existing.data[0].number, | |
| body: `Another nightly failure on ${today}.\n\n[View run](${runUrl})`, | |
| }); | |
| } else { | |
| await github.rest.issues.create({ | |
| owner, repo, | |
| title: `Nightly CI failure: ${today}`, | |
| body: `One or more nightly jobs failed.\n\n[View workflow run](${runUrl})`, | |
| labels: ['nightly-failure'], | |
| }); | |
| } | |
| - name: Fail on nightly errors | |
| if: contains(needs.*.result, 'failure') | |
| run: | | |
| echo "::error::One or more nightly jobs failed. Check the workflow run for details." | |
| exit 1 |