diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c0aefa..49856ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,48 +22,13 @@ concurrency: cancel-in-progress: true jobs: - # Fast version consistency check - version-check: - name: Version Sync - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - uses: actions/checkout@v4 - - - name: Check Python ↔ Rust version consistency - run: | - PYTHON_VERSION=$(grep -E "^version = " pyproject.toml | head -1 | cut -d'"' -f2) - RUST_VERSION=$(grep -E "^version = " rust/Cargo.toml | head -1 | cut -d'"' -f2) - - echo "Python: $PYTHON_VERSION" - echo "Rust: $RUST_VERSION" - - if [ "$PYTHON_VERSION" != "$RUST_VERSION" ]; then - echo "❌ Version mismatch!" - exit 1 - fi - echo "✅ Versions in sync: $PYTHON_VERSION" - - # Fast format & lint checks (parallel) + # Lint, format, type check (runs on all events) quick-check: name: Format & Lint - runs-on: ubuntu-latest + runs-on: cachekit timeout-minutes: 10 steps: - - uses: actions/checkout@v4 - - - name: Install uv - uses: astral-sh/setup-uv@v3 - with: - enable-cache: true - - - name: Set up Python - run: uv python install ${{ env.DEFAULT_PYTHON_VERSION }} - - - name: Set up Rust - uses: dtolnay/rust-toolchain@stable - with: - components: rustfmt, clippy + - uses: actions/checkout@v6 - name: Cache Rust dependencies uses: Swatinem/rust-cache@v2 @@ -71,7 +36,7 @@ jobs: workspaces: rust - name: Cache Python virtual environment - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: .venv key: venv-${{ runner.os }}-py${{ env.DEFAULT_PYTHON_VERSION }}-${{ hashFiles('**/pyproject.toml', '**/uv.lock', 'rust/**/*.rs', 'rust/**/Cargo.toml') }} @@ -79,44 +44,32 @@ jobs: venv-${{ runner.os }}-py${{ env.DEFAULT_PYTHON_VERSION }}- - name: Install dependencies (if not cached) - run: | - uv sync --group dev + run: uv sync --group dev - name: Check Python formatting - run: | - uv run ruff format --check . + run: uv run ruff format --check . - name: Lint Python if: success() || failure() - run: | - uv run ruff check . - - - name: Scan Python dependencies for CVEs - if: success() || failure() - run: | - uv run pip-audit --desc + run: uv run ruff check . - name: Type check Python if: success() || failure() - run: | - uv run basedpyright --level error + run: uv run basedpyright --level error - name: Check Rust formatting if: success() || failure() - run: | - cd rust && cargo fmt --check + run: cd rust && cargo fmt --check - name: Lint Rust if: success() || failure() - run: | - cd rust && cargo clippy -- -D warnings + run: cd rust && cargo clippy -- -D warnings - # Critical test suite (parallel matrix) - # Push to main: runs on lab self-hosted runners (consistent perf, free minutes) - # PRs: runs on GitHub-hosted runners (untrusted code never touches lab infra) - test-critical: + # PR: single Python version, critical tests only + # Push: full matrix, full test suite + test: name: Tests (Python ${{ matrix.python-version }}) - runs-on: ${{ github.event_name == 'push' && 'cachekit' || 'ubuntu-latest' }} + runs-on: cachekit timeout-minutes: 15 permissions: contents: read @@ -124,11 +77,14 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ${{ github.event_name == 'push' && fromJSON('["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]') || fromJSON('["3.12"]') }} services: redis: - image: redis:7-alpine + image: redis@sha256:4bfd9eca23339865dc14fe75f6d9ae643f714924623978dd2798f1a673b08f43 # redis:7-alpine amd64 + credentials: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} ports: - 6379:6379 options: >- @@ -138,18 +94,7 @@ jobs: --health-retries 5 steps: - - uses: actions/checkout@v4 - - - name: Install uv - uses: astral-sh/setup-uv@v3 - with: - enable-cache: true - - - name: Set up Python - run: uv python install ${{ matrix.python-version }} - - - name: Set up Rust - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@v6 - name: Cache Rust dependencies uses: Swatinem/rust-cache@v2 @@ -157,7 +102,7 @@ jobs: workspaces: rust - name: Cache Python virtual environment - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: .venv key: venv-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('**/pyproject.toml', '**/uv.lock', 'rust/**/*.rs', 'rust/**/Cargo.toml') }} @@ -165,8 +110,7 @@ jobs: venv-${{ runner.os }}-py${{ matrix.python-version }}- - name: Install dependencies (if not cached) - run: | - uv sync --group dev + run: uv sync --python ${{ matrix.python-version }} --group dev - name: Run critical tests (PRs) if: github.event_name == 'pull_request' @@ -212,60 +156,50 @@ jobs: flags: ${{ github.event_name == 'push' && 'full' || 'critical' }}-python-${{ matrix.python-version }} fail_ci_if_error: false - # Markdown documentation tests - test-docs: - name: Documentation Examples - runs-on: ubuntu-latest + # Version sync + doc tests (push to main only) + post-merge: + name: Post-Merge Checks + if: github.event_name == 'push' + runs-on: cachekit timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - - name: Install uv - uses: astral-sh/setup-uv@v3 - with: - enable-cache: true - - - name: Set up Python - run: uv python install ${{ env.DEFAULT_PYTHON_VERSION }} - - - name: Set up Rust - uses: dtolnay/rust-toolchain@stable + - name: Check Python ↔ Rust version consistency + run: | + PYTHON_VERSION=$(grep -E "^version = " pyproject.toml | head -1 | cut -d'"' -f2) + RUST_VERSION=$(grep -E "^version = " rust/Cargo.toml | head -1 | cut -d'"' -f2) + if [ "$PYTHON_VERSION" != "$RUST_VERSION" ]; then + echo "Version mismatch: Python=$PYTHON_VERSION Rust=$RUST_VERSION" + exit 1 + fi - name: Cache Rust dependencies uses: Swatinem/rust-cache@v2 with: workspaces: rust - - name: Cache Python virtual environment - uses: actions/cache@v4 - with: - path: .venv - key: venv-${{ runner.os }}-py${{ env.DEFAULT_PYTHON_VERSION }}-${{ hashFiles('**/pyproject.toml', '**/uv.lock', 'rust/**/*.rs', 'rust/**/Cargo.toml') }} - restore-keys: | - venv-${{ runner.os }}-py${{ env.DEFAULT_PYTHON_VERSION }}- - - name: Install dependencies (if not cached) - run: | - uv sync --group dev + run: uv sync --group dev + + - name: Scan Python dependencies for CVEs + run: uv run pip-audit --desc - name: Run markdown documentation tests - run: | - make test-docs-examples + run: make test-docs-examples # Summary job (required for branch protection) ci-success: name: CI Success runs-on: ubuntu-latest - needs: [version-check, quick-check, test-critical, test-docs] + needs: [quick-check, test] if: always() steps: - name: Check all jobs succeeded run: | - if [[ "${{ needs.version-check.result }}" != "success" ]] || \ - [[ "${{ needs.quick-check.result }}" != "success" ]] || \ - [[ "${{ needs.test-critical.result }}" != "success" ]] || \ - [[ "${{ needs.test-docs.result }}" != "success" ]]; then - echo "❌ One or more jobs failed" + if [[ "${{ needs.quick-check.result }}" != "success" ]] || \ + [[ "${{ needs.test.result }}" != "success" ]]; then + echo "One or more jobs failed" exit 1 fi - echo "✅ All CI checks passed" + echo "All CI checks passed"