# Main quality gate (task 42). # # Per-PR legs (all must be green, except `coverage` which is report-only): # fmt, clippy, audit, deny, sqlx-prepare-check, nextest, doc-tests, # coverage (llvm-cov report upload over lib unit tests; non-blocking), # fuzz-build (nightly leg inside `fuzz/`, so the pinned stable # `rust-toolchain.toml` at the root is never violated). # Scheduled legs (not per-PR): # mutants (weekly budgeted mutation run over the pure security modules), # fuzz-run (bounded run of every fuzz target; the per-PR leg only builds), # miri (scoped interpreter run over the sync crypto/JOSE helpers; # allow-failure, never gates). # # Must NOT gate on miri for tokio/sqlx/redis; no lint suppressions anywhere. # The `miri` leg therefore runs only sync unit tests (`crypto::{ct, pkce, # tokens}` and the sync subset of `jose::jar` — the fetch-decode, # request-URI-membership, and alg-rejection tests, which short-circuit before # any crypto or I/O), excluding the argon2 KDF # (`crypto::secret`, cost prohibitive under the interpreter), the `ring`-backed # modules (`crypto::field`, `crypto::totp`, whose C/asm calls the interpreter # cannot execute), and every # `#[tokio::test]` / redis-backed test (DPoP replay store, async claim # paths, including the async `jose::jar::resolve` path itself — only its sync # unit tests are selected); the whole leg is `continue-on-error` so it can # never gate a PR. If a `#[tokio::test]` or a real-verification test is ever # added under `jose::jar`, narrow the filter to the explicit sync test paths. name: ci on: push: branches: ["master"] pull_request: {} schedule: # Weekly budgeted mutation run (Sunday 03:00 UTC). - cron: "0 3 * * 0" workflow_dispatch: {} permissions: contents: read env: CARGO_TERM_COLOR: always SQLX_OFFLINE: "true" # Shared compilation cache (each compiling job below starts it via # sccache-action, except `miri`: cargo-miri overrides RUSTC_WRAPPER # itself). Incremental is useless on ephemeral runners, and disabling it # maximizes cache hits. SCCACHE_GHA_ENABLED: "true" CARGO_INCREMENTAL: "0" jobs: fmt: name: fmt runs-on: ubuntu-24.04 steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable with: components: rustfmt - name: Check formatting run: cargo fmt --check clippy: name: clippy runs-on: ubuntu-24.04 env: RUSTC_WRAPPER: sccache steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable with: components: clippy - name: Start sccache (shared compilation cache) uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11 with: version: "v0.18.0" - name: Clippy (all targets, warnings denied) run: cargo clippy --all-targets -- -D warnings audit: name: audit runs-on: ubuntu-24.04 steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable - name: Install cargo-audit uses: taiki-e/install-action@ab6895351a88b908e96a4697860d4ffea5e57dbe # v2 with: tool: cargo-audit - name: Audit advisories run: cargo audit deny: name: deny runs-on: ubuntu-24.04 steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable - name: Install cargo-deny uses: taiki-e/install-action@ab6895351a88b908e96a4697860d4ffea5e57dbe # v2 with: tool: cargo-deny - name: Check supply chain (ring-only TLS enforced in deny.toml) run: cargo deny check sqlx-prepare-check: name: sqlx-prepare-check runs-on: ubuntu-24.04 env: RUSTC_WRAPPER: sccache services: postgres: image: postgres:16 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: sqlxcheck POSTGRES_DB: postgres ports: - 5432:5432 options: >- --health-cmd "pg_isready -U postgres" --health-interval 5s --health-timeout 5s --health-retries 10 steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable - name: Start sccache (shared compilation cache) uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11 with: version: "v0.18.0" - name: Install sqlx-cli uses: taiki-e/install-action@ab6895351a88b908e96a4697860d4ffea5e57dbe # v2 with: tool: sqlx-cli@0.9.0 - name: Apply migrations to the check database env: DATABASE_URL: postgres://postgres:sqlxcheck@127.0.0.1:5432/postgres run: cargo sqlx migrate run - name: Fail if the committed .sqlx cache is stale env: DATABASE_URL: postgres://postgres:sqlxcheck@127.0.0.1:5432/postgres run: cargo sqlx prepare --check --workspace doc-links: name: doc-links runs-on: ubuntu-24.04 steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 with: python-version: "3.x" - name: Check documentation links (markdown-link-check equivalent) run: python3 scripts/check_doc_links.py nextest: name: nextest runs-on: ubuntu-24.04 env: RUSTC_WRAPPER: sccache steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable - name: Start sccache (shared compilation cache) uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11 with: version: "v0.18.0" - name: Install cargo-nextest uses: taiki-e/install-action@ab6895351a88b908e96a4697860d4ffea5e57dbe # v2 with: tool: nextest - name: Install redis-server (in-process Sentinel/Cluster fixtures) run: sudo apt-get update && sudo apt-get install -y redis-server - name: Start shared test services (Postgres + Redis) run: docker compose -f compose.test.yaml up -d - name: Full test suite (shared services; testcontainers for lifecycle) run: cargo nextest run --all-targets - name: Stop shared test services if: always() run: docker compose -f compose.test.yaml down -v doc-tests: name: doc-tests runs-on: ubuntu-24.04 env: RUSTC_WRAPPER: sccache steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable - name: Start sccache (shared compilation cache) uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11 with: version: "v0.18.0" - name: Documentation tests run: cargo test --doc fuzz-build: name: fuzz-build runs-on: ubuntu-24.04 env: RUSTC_WRAPPER: sccache defaults: run: working-directory: fuzz steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 # The `fuzz/rust-toolchain.toml` in the working directory selects # nightly via rustup override; the root stable pin is untouched. - name: Install cargo-fuzz (nightly, per fuzz/rust-toolchain.toml) uses: taiki-e/install-action@ab6895351a88b908e96a4697860d4ffea5e57dbe # v2 with: tool: cargo-fuzz - name: Start sccache (shared compilation cache) uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11 with: version: "v0.18.0" # Explicit gnu target: the prebuilt cargo-fuzz binary is musl-static, # so its default target is musl (ASan-incompatible, and musl std is # not installed). Never drop this flag without checking the default. - name: Build all fuzz targets run: cargo fuzz build --target x86_64-unknown-linux-gnu fuzz-run: name: fuzz-run # Scheduled bounded fuzzing. `fuzz-build` (above) only compiles the # targets per PR; this leg actually executes them, so a crash is found # without a human starting it. It is bounded and scheduled, not 24/7: # continuous coverage is ClusterFuzzLite (`.clusterfuzzlite/`, docs in # `docs/CONTRIBUTING.md`); the remaining owner step is enabling it. if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-24.04 env: RUSTC_WRAPPER: sccache timeout-minutes: 70 strategy: fail-fast: false matrix: target: [token_form, dcr_json, jar_claims, dpop_proof, jar_verify, redirect_uri, scope_parse] defaults: run: working-directory: fuzz steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 # `fuzz/rust-toolchain.toml` selects nightly; the root stable pin is # untouched, exactly as in `fuzz-build`. - name: Install cargo-fuzz (nightly) uses: taiki-e/install-action@ab6895351a88b908e96a4697860d4ffea5e57dbe # v2 with: tool: cargo-fuzz - name: Start sccache (shared compilation cache) uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11 with: version: "v0.18.0" # `uses` steps resolve paths from the workspace root, not the job's # `working-directory`; keep the fuzz/ prefix explicit. - name: Restore corpus for ${{ matrix.target }} uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5 with: path: fuzz/corpus/${{ matrix.target }} key: fuzz-corpus-${{ matrix.target }}-${{ github.run_id }} restore-keys: | fuzz-corpus-${{ matrix.target }}- # Same explicit target as `fuzz-build`: cargo-fuzz defaults to the # installer binary's own platform (musl), not the runner's (gnu). - name: Fuzz ${{ matrix.target }} (bounded) run: cargo fuzz run --target x86_64-unknown-linux-gnu ${{ matrix.target }} -- -max_total_time=600 -rss_limit_mb=2048 # Save only on success; a per-run key grows the corpus while the prefix # `restore-keys` always pick the newest entry. - name: Save corpus for ${{ matrix.target }} if: success() uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5 with: path: fuzz/corpus/${{ matrix.target }} key: fuzz-corpus-${{ matrix.target }}-${{ github.run_id }} - name: Upload crash artifacts if: failure() uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: fuzz-crash-${{ matrix.target }} path: fuzz/artifacts/${{ matrix.target }} if-no-files-found: ignore mutants: name: mutants # Scheduled budgeted job only: never blocks a PR. if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-24.04 env: RUSTC_WRAPPER: sccache timeout-minutes: 60 steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable - name: Start sccache (shared compilation cache) uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11 with: version: "v0.18.0" - name: Install cargo-mutants uses: taiki-e/install-action@ab6895351a88b908e96a4697860d4ffea5e57dbe # v2 with: tool: cargo-mutants - name: Budgeted mutation run (see .cargo/mutants.toml) run: cargo mutants --timeout 1800 --test-tool nextest coverage: name: coverage # Report-only coverage signal (T4-4). Non-blocking by design: the upload # is the deliverable; there is no `--fail-under-*` threshold yet. # Services mirror `nextest` (shared Postgres/Redis plus redis-server), # because several lib unit tests are testcontainers-backed. # Follow-up: add a threshold and extend beyond `--lib` once # the first reports land. runs-on: ubuntu-24.04 env: RUSTC_WRAPPER: sccache continue-on-error: true steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable with: components: llvm-tools-preview - name: Start sccache (shared compilation cache) uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11 with: version: "v0.18.0" - name: Install cargo-llvm-cov uses: taiki-e/install-action@ab6895351a88b908e96a4697860d4ffea5e57dbe # v2 with: tool: cargo-llvm-cov - name: Install redis-server (in-process Sentinel/Cluster fixtures) run: sudo apt-get update && sudo apt-get install -y redis-server - name: Start shared test services (Postgres + Redis) run: docker compose -f compose.test.yaml up -d - name: Coverage report (lib unit tests only; integration tests stay in `nextest`) run: cargo llvm-cov --lib --lcov --output-path lcov.info - name: Stop shared test services if: always() run: docker compose -f compose.test.yaml down -v - name: Upload coverage report if: always() uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: coverage-lcov path: lcov.info if-no-files-found: error miri: name: miri # Scoped miri leg (T4-4). Scheduled only: the interpreter is slow and # nightly-only, so this never runs per-PR and never gates # (`continue-on-error`). Nightly is confined to this job; the root # `rust-toolchain.toml` stable pin is untouched. # Scope is the sync unit tests of the dependency-free helpers, plus the # sync subset of `jose::jar` (fetch-decode, request-URI-membership, and # alg-rejection tests only — never the async `resolve` path). # Explicitly excluded, per the header constraint: tokio (`#[tokio::test]`), sqlx, and redis # (DPoP replay store, async claim paths), plus the argon2 KDF # (`crypto::secret`, cost prohibitive under the interpreter) and the # `ring`-backed modules (`crypto::field`, `crypto::totp`, whose C/asm # calls Miri cannot interpret). if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-24.04 timeout-minutes: 60 continue-on-error: true steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - uses: dtolnay/rust-toolchain@f0d5300ba8908d942c808f4d31c08471d7798ba3 # nightly with: components: miri - name: Scoped miri run (sync crypto/JOSE helpers only) env: # Relaxed isolation for the first scheduled runs (wall-clock/env # access); tighten in a follow-up once green. MIRIFLAGS: -Zmiri-disable-isolation run: > cargo miri test --lib -- crypto::ct crypto::pkce crypto::tokens jose::jar