security: make the secret-detection gates actually reject (#45) #424
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", "develop", "feature/*"] | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # Stage 1: Quick quality checks (fail fast on basic issues) | |
| quality-checks: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v3 | |
| - uses: actions/setup-python@v4 | |
| with: | |
| python-version: "3.12" | |
| - name: Install uv | |
| run: | | |
| curl -LsSf https://astral.sh/uv/install.sh | sh | |
| echo "$HOME/.cargo/bin" >> $GITHUB_PATH | |
| - name: Install dependencies | |
| # Lockfile-pinned: lint/format/type results must be reproducible. | |
| # `uv pip install -e ".[dev]"` ignores uv.lock and resolves floors | |
| # (ruff>=0.12.0) to whatever is newest, so CI silently drifted to | |
| # ruff 0.16 / mypy 2.3 while uv.lock pins 0.12.5 / 1.17.0. | |
| # --locked (not --frozen) so a lockfile that has drifted from | |
| # pyproject.toml fails the build instead of installing a stale set. | |
| run: uv sync --locked --all-extras | |
| - name: Secret Detection | |
| run: ./scripts/ci/secret-detection.sh | |
| - name: Lint (ruff) | |
| run: uv run --locked ruff check . | |
| - name: Format (ruff) | |
| run: uv run --locked ruff format --check . | |
| - name: Type Check (mypy) | |
| run: uv run --locked mypy . | |
| # Stage 2: Light smoke test (quick test run before full matrix) | |
| light-smoke-test: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: quality-checks | |
| steps: | |
| - uses: actions/checkout@v3 | |
| - uses: actions/setup-python@v4 | |
| with: | |
| python-version: "3.12" | |
| - name: Install uv | |
| run: | | |
| curl -LsSf https://astral.sh/uv/install.sh | sh | |
| echo "$HOME/.cargo/bin" >> $GITHUB_PATH | |
| - name: Install dependencies | |
| run: | | |
| uv venv --python python3.12 | |
| uv pip install -e ".[dev]" | |
| - name: Run basic smoke tests | |
| run: | | |
| # Run only critical tests to quickly catch obvious failures | |
| source .venv/bin/activate | |
| python -m pytest tests/unit/test_imports.py tests/unit/test_louie_factory.py -v --tb=short | |
| # Stage 3: Unit tests (only run if smoke test passes) | |
| unit-tests: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| needs: [quality-checks, light-smoke-test] | |
| strategy: | |
| matrix: | |
| python-version: ["3.10", "3.11", "3.12", "3.13"] | |
| steps: | |
| - uses: actions/checkout@v3 | |
| - uses: actions/setup-python@v4 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Install uv | |
| run: | | |
| curl -LsSf https://astral.sh/uv/install.sh | sh | |
| echo "$HOME/.cargo/bin" >> $GITHUB_PATH | |
| - name: Install dependencies | |
| run: | | |
| uv venv --python python${{ matrix.python-version }} | |
| uv pip install -e ".[dev]" | |
| - name: Run unit tests | |
| run: | | |
| # Run only unit tests with coverage | |
| source .venv/bin/activate | |
| python -m pytest tests/unit/ -v --cov=louieai --cov-report=xml --cov-report=term --cov-fail-under=85 | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v3 | |
| with: | |
| file: ./coverage.xml | |
| flags: unit | |
| name: Unit Tests | |
| fail_ci_if_error: false # Don't fail if Codecov is down | |
| # Stage 3b: Targeted pandas version checks | |
| unit-tests-pandas-compat: | |
| name: unit-tests-pandas (${{ matrix.name }}) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| needs: [quality-checks, light-smoke-test] | |
| strategy: | |
| matrix: | |
| include: | |
| - name: pandas2-py310 | |
| python-version: "3.10" | |
| pandas-version: "pandas>=2,<3" | |
| - name: pandas3-py313 | |
| python-version: "3.13" | |
| pandas-version: "pandas>=3,<4" | |
| steps: | |
| - uses: actions/checkout@v3 | |
| - uses: actions/setup-python@v4 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Install uv | |
| run: | | |
| curl -LsSf https://astral.sh/uv/install.sh | sh | |
| echo "$HOME/.cargo/bin" >> $GITHUB_PATH | |
| - name: Install dependencies | |
| run: | | |
| uv venv --python python${{ matrix.python-version }} | |
| uv pip install -e ".[dev]" | |
| uv pip install "${{ matrix.pandas-version }}" | |
| - name: Run unit tests | |
| run: | | |
| source .venv/bin/activate | |
| python -m pytest tests/unit/ -v | |
| # Stage 4: Integration tests (conditional on credentials) | |
| integration-tests: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| needs: quality-checks | |
| # Only run if we have credentials (for PRs from the main repo) | |
| if: | | |
| github.event_name == 'push' || | |
| (github.event.pull_request.head.repo.full_name == github.repository) | |
| steps: | |
| - uses: actions/checkout@v3 | |
| - uses: actions/setup-python@v4 | |
| with: | |
| python-version: "3.12" | |
| - name: Install uv | |
| run: | | |
| curl -LsSf https://astral.sh/uv/install.sh | sh | |
| echo "$HOME/.cargo/bin" >> $GITHUB_PATH | |
| - name: Install dependencies | |
| run: | | |
| uv venv --python python3.12 | |
| uv pip install -e ".[dev]" | |
| - name: Run integration tests | |
| env: | |
| GRAPHISTRY_SERVER: ${{ secrets.GRAPHISTRY_SERVER }} | |
| GRAPHISTRY_USERNAME: ${{ secrets.GRAPHISTRY_USERNAME }} | |
| GRAPHISTRY_PASSWORD: ${{ secrets.GRAPHISTRY_PASSWORD }} | |
| LOUIE_SERVER: ${{ secrets.LOUIE_SERVER }} | |
| run: | | |
| # Only run if credentials are configured | |
| if [ -n "$GRAPHISTRY_USERNAME" ]; then | |
| uv run --locked pytest tests/integration/ -v --cov=louieai --cov-report=xml | |
| else | |
| echo "⚠️ Skipping integration tests - no credentials configured" | |
| fi | |
| - name: Upload coverage to Codecov | |
| if: env.GRAPHISTRY_USERNAME != '' | |
| uses: codecov/codecov-action@v3 | |
| with: | |
| file: ./coverage.xml | |
| flags: integration | |
| name: Integration Tests | |
| fail_ci_if_error: false | |
| # Stage 5: Documentation tests (can run in parallel with other tests) | |
| docs-test: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| needs: quality-checks | |
| steps: | |
| - uses: actions/checkout@v3 | |
| - uses: actions/setup-python@v4 | |
| with: | |
| python-version: "3.12" | |
| - name: Install uv | |
| run: | | |
| curl -LsSf https://astral.sh/uv/install.sh | sh | |
| echo "$HOME/.cargo/bin" >> $GITHUB_PATH | |
| - name: Install dependencies | |
| # Lockfile-pinned: `mkdocs build --strict` is a determinism gate, so a | |
| # floating mkdocs/plugin version must not change pass/fail. See the | |
| # matching note in quality-checks. | |
| run: uv sync --locked --all-extras | |
| - name: Test documentation examples | |
| run: | | |
| uv run --locked pytest tests/unit/test_documentation.py -v | |
| - name: Validate ReadTheDocs config | |
| run: | | |
| curl -sSL https://raw.githubusercontent.com/readthedocs/readthedocs.org/main/readthedocs/rtd_tests/fixtures/spec/v2/schema.json -o rtd-schema.json | |
| # jsonschema and pyyaml already come from `uv sync --all-extras`; a | |
| # bare `uv pip install` here would perturb the pinned environment. | |
| uv run --locked python -c " | |
| import yaml, json, jsonschema | |
| with open('.readthedocs.yml') as f: | |
| config = yaml.safe_load(f) | |
| with open('rtd-schema.json') as f: | |
| schema = json.load(f) | |
| jsonschema.validate(config, schema) | |
| print('✅ ReadTheDocs config is valid') | |
| " | |
| - name: Build documentation | |
| run: uv run --locked mkdocs build --strict | |
| - name: Verify logo included | |
| run: | | |
| test -f site/assets/louie-logo.png || (echo "❌ Logo not found in built docs" && exit 1) | |
| echo "✅ Logo included in documentation" | |
| # Stage 6: Build and installation tests (only if unit tests pass) | |
| install-test: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| needs: unit-tests | |
| strategy: | |
| matrix: | |
| installer: ["pip", "uv"] | |
| python-version: ["3.10", "3.11", "3.12", "3.13"] | |
| steps: | |
| - uses: actions/checkout@v3 | |
| - uses: actions/setup-python@v4 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Install build tools | |
| run: | | |
| pip install --upgrade pip | |
| pip install build | |
| - name: Build package | |
| run: python -m build | |
| - name: Install uv (if needed) | |
| if: matrix.installer == 'uv' | |
| run: | | |
| curl -LsSf https://astral.sh/uv/install.sh | sh | |
| echo "$HOME/.cargo/bin" >> $GITHUB_PATH | |
| - name: Test pip installation | |
| if: matrix.installer == 'pip' | |
| run: | | |
| python -m venv test_env | |
| source test_env/bin/activate | |
| pip install dist/*.whl | |
| python -c "import louieai; print(f'✅ louieai {louieai.__version__} installed')" | |
| python -c "from louieai import louie, Cursor; print('✅ louie and Cursor imported')" | |
| python -c "from louieai import louie; lui = louie(); print('✅ louie() factory works')" | |
| - name: Test uv installation | |
| if: matrix.installer == 'uv' | |
| run: | | |
| uv venv test_env | |
| source test_env/bin/activate | |
| uv pip install dist/*.whl | |
| python -c "import louieai; print(f'✅ louieai {louieai.__version__} installed')" | |
| python -c "from louieai import louie, Cursor; print('✅ louie and Cursor imported')" | |
| python -c "from louieai import louie; lui = louie(); print('✅ louie() factory works')" | |
| # Final summary job (for branch protection rules) | |
| ci-success: | |
| runs-on: ubuntu-latest | |
| needs: [quality-checks, light-smoke-test, unit-tests, unit-tests-pandas-compat, docs-test, install-test] | |
| # Note: integration-tests is not required since it's conditional | |
| if: always() | |
| steps: | |
| - name: Check all required jobs | |
| run: | | |
| if [[ "${{ needs.quality-checks.result }}" != "success" || \ | |
| "${{ needs.unit-tests.result }}" != "success" || \ | |
| "${{ needs.unit-tests-pandas-compat.result }}" != "success" || \ | |
| "${{ needs.docs-test.result }}" != "success" || \ | |
| "${{ needs.install-test.result }}" != "success" ]]; then | |
| echo "❌ One or more required jobs failed" | |
| exit 1 | |
| fi | |
| echo "✅ All required jobs passed" |