# .github/workflows/ci.yml — Picarones CI/CD # # Pipeline GitHub Actions (mis à jour Sprint A1 — Hardening CI) : # - Tests sur Python 3.11 / 3.12 / 3.13 (3.13 informationnel, 6 mois) # - Linux, macOS, Windows # - Couverture exigée >= 85 % (--cov-fail-under, plancher 2 pts sous baseline 87 %) # - Timeout pytest 5 min par test individuel (pytest-timeout, mode thread) # - Type-check mypy (strict sur picarones/core/, lax ailleurs — durci en A11) # - Scanners sécurité : bandit (statique) + pip-audit (CVE deps) + trivy (image) # - Build de la distribution Python # - Vérification de l'exécutable demo name: CI on: push: branches: [main, develop, "feature/**", "sprint/**", "claude/**"] pull_request: branches: [main, develop] workflow_dispatch: # Déclenchement manuel permissions: contents: read # ────────────────────────────────────────────────────────────────── # Job 1 : Tests unitaires et d'intégration # ────────────────────────────────────────────────────────────────── jobs: tests: name: Tests Python ${{ matrix.python-version }} / ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] # 3.13 ajouté Sprint A1 (item m-8). Reste informationnel # (continue-on-error sur Linux uniquement) pendant 6 mois pour # tracker la compat sans bloquer. python-version: ["3.11", "3.12", "3.13"] include: - python-version: "3.13" os: ubuntu-latest experimental: true steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: pip # ── Tesseract ────────────────────────────────────────────── - name: Install Tesseract (Ubuntu) if: runner.os == 'Linux' run: | sudo apt-get update -qq sudo apt-get install -y tesseract-ocr tesseract-ocr-fra tesseract-ocr-lat - name: Install Tesseract (macOS) if: runner.os == 'macOS' run: | brew install tesseract tesseract-lang env: HOMEBREW_NO_AUTO_UPDATE: "1" - name: Install Tesseract (Windows) if: runner.os == 'Windows' run: | choco install tesseract -y echo "C:\Program Files\Tesseract-OCR" >> $env:GITHUB_PATH shell: pwsh # ── Dépendances Python ────────────────────────────────────── # Mise à jour pip/setuptools/wheel en début de job (Sprint A1) pour # éviter les CVE qui dorment dans les images runner GitHub. - name: Install dependencies run: | python -m pip install --upgrade pip setuptools wheel pip install -e ".[dev,web]" # ── Tests ─────────────────────────────────────────────────── # Sprint A1 : --cov-fail-under=85 (baseline mesuré 87 %, marge 2 pts). # pytest-timeout est configuré dans pyproject.toml [tool.pytest.ini_options]. - name: Run tests # Sur Python 3.13, on continue malgré une erreur pour ne pas bloquer # le merge pendant la fenêtre informationnelle de 6 mois (m-8). continue-on-error: ${{ matrix.python-version == '3.13' }} shell: bash run: | pytest tests/ -q --tb=short --no-header \ --cov=picarones --cov-report=xml --cov-report=term-missing \ --cov-fail-under=85 env: PYTHONIOENCODING: utf-8 PYTHONUTF8: "1" # ── Couverture ────────────────────────────────────────────── - name: Upload coverage to Codecov if: runner.os == 'Linux' && matrix.python-version == '3.11' && env.CODECOV_TOKEN != '' uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml flags: unittests name: picarones-coverage fail_ci_if_error: true env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} # ────────────────────────────────────────────────────────────────── # Job 2 : Vérification du rapport demo # ────────────────────────────────────────────────────────────────── demo: name: Demo end-to-end runs-on: ubuntu-latest needs: tests steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" cache: pip - name: Install Tesseract run: | sudo apt-get update -qq sudo apt-get install -y tesseract-ocr tesseract-ocr-fra - name: Install Picarones run: pip install -e ".[web]" - name: Run demo run: | picarones demo --docs 12 --output rapport_demo_ci.html \ --with-history --with-robustness ls -lh rapport_demo_ci.html # Vérifier que le fichier est valide et contient les sections attendues python -c " content = open('rapport_demo_ci.html').read() assert 'Picarones' in content, 'Picarones non trouvé dans le rapport' assert 'CER' in content, 'CER non trouvé dans le rapport' assert len(content) > 50000, f'Rapport trop petit : {len(content)} octets' print(f'Rapport OK : {len(content):,} octets') " - name: Upload demo report as artifact uses: actions/upload-artifact@v4 with: name: rapport-demo path: rapport_demo_ci.html retention-days: 7 # ────────────────────────────────────────────────────────────────── # Job 3 : Build de la distribution Python # ────────────────────────────────────────────────────────────────── build: name: Build distribution runs-on: ubuntu-latest needs: tests steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" cache: pip - name: Install build tools run: pip install --upgrade build twine - name: Build wheel and sdist run: python -m build - name: Check distribution run: twine check dist/* - name: Upload distribution as artifact uses: actions/upload-artifact@v4 with: name: dist-packages path: dist/ retention-days: 30 # ────────────────────────────────────────────────────────────────── # Job 4 : Vérification de la qualité du code # La config ruff est centralisée dans pyproject.toml ([tool.ruff]), # donc CI, Makefile et invocations locales produisent exactement les # mêmes résultats — pas de divergence possible entre flags. # ────────────────────────────────────────────────────────────────── lint: name: Code quality runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" cache: pip - name: Install ruff run: pip install ruff - name: Run ruff run: ruff check picarones/ tests/ # ────────────────────────────────────────────────────────────────── # Job 5 : Type-checking — Sprint A1 (item M-4) # # mypy est configuré dans pyproject.toml [tool.mypy] : # - strict sur picarones.core.* (10 modules) # - lax ailleurs (follow_imports=silent) # Deux checks pré-existants désactivés (disallow_any_generics et # warn_return_any), à ré-activer en Sprint A11 après fix des # ~100 erreurs baseline. # ────────────────────────────────────────────────────────────────── typecheck: name: Type checking (mypy) runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" cache: pip - name: Install dependencies run: | python -m pip install --upgrade pip setuptools wheel pip install -e ".[dev,web,stats]" - name: Run mypy on picarones/core (strict) run: python -m mypy picarones/core/ # ────────────────────────────────────────────────────────────────── # Job 6 : Sécurité — Sprint A1 (item B-7) # # bandit : scan statique du code Python (HIGH/MEDIUM bloquants). # pip-audit : CVEs des dépendances installées. # trivy : scan du Dockerfile + image résultante (HIGH/CRITICAL bloquants). # # Configuration bandit dans pyproject.toml [tool.bandit] avec exclusions # documentées (B310, B608, B615, B701 — chacune avec sprint cible). # ────────────────────────────────────────────────────────────────── security: name: Security scanners runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" cache: pip - name: Install scanners run: | python -m pip install --upgrade pip setuptools wheel pip install -e ".[dev,web]" # bandit : -ll = LOW + above. La config pyproject.toml exclut les # informationnels acceptés ; tout HIGH/MEDIUM nouveau bloque. - name: Run bandit run: python -m bandit -r picarones/ -ll -c pyproject.toml # pip-audit : --skip-editable car picarones n'est pas sur PyPI. # En CI, l'env est frais (pip à jour grâce au step précédent) donc # les CVEs trouvées sont des vraies vulnérabilités déclarées. - name: Run pip-audit run: python -m pip_audit --skip-editable --strict # trivy : scan du Dockerfile + image résultante. Build local pour # auditer ce qui sera réellement déployé sur HuggingFace Space. - name: Build Docker image for scan run: docker build -t picarones:ci-scan . - name: Run Trivy vulnerability scanner (image) uses: aquasecurity/trivy-action@0.28.0 with: image-ref: 'picarones:ci-scan' format: 'table' exit-code: '1' ignore-unfixed: true severity: 'HIGH,CRITICAL' vuln-type: 'os,library' - name: Run Trivy on Dockerfile (config scan) uses: aquasecurity/trivy-action@0.28.0 with: scan-type: 'config' scan-ref: 'Dockerfile' exit-code: '1' severity: 'HIGH,CRITICAL' # ────────────────────────────────────────────────────────────────── # Job 7 : CI/CD — Détection de régression CER (optionnel) # Commenté par défaut — activer si vous avez un corpus de référence # ────────────────────────────────────────────────────────────────── # regression-check: # name: Regression check # runs-on: ubuntu-latest # needs: tests # if: github.event_name == 'pull_request' # # steps: # - name: Checkout # uses: actions/checkout@v4 # # - name: Install # run: pip install -e . # # - name: Run benchmark on reference corpus # run: | # picarones run \ # --corpus ./tests/fixtures/reference_corpus/ \ # --engines tesseract \ # --output results_pr.json \ # --fail-if-cer-above 15.0