Skip to content

release(3.0.0): consolidate Dependabot #276/#278–#284 + fold #285 + CHANGELOG release line #1771

release(3.0.0): consolidate Dependabot #276/#278–#284 + fold #285 + CHANGELOG release line

release(3.0.0): consolidate Dependabot #276/#278–#284 + fold #285 + CHANGELOG release line #1771

Workflow file for this run

name: CI - Build and Test
on:
push:
branches: [ main, develop, 'claude/**' ]
pull_request:
branches: [ main, develop ]
workflow_dispatch:
# Collapse overlapping runs on the same ref; feature branches and PR
# heads cancel in-flight runs, main is preserved.
concurrency:
group: ci-build-test-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' && github.event_name != 'schedule' }}
jobs:
# C Library Build and Test
c-library:
name: C Library (${{ matrix.os }}, ${{ matrix.compiler }})
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
compiler: [gcc, clang]
include:
- os: ubuntu-latest
compiler: gcc
cc: gcc-13
cxx: g++-13
- os: ubuntu-latest
compiler: clang
cc: clang-18
cxx: clang++-18
- os: macos-latest
compiler: gcc
cc: gcc-12
cxx: g++-12
- os: macos-latest
compiler: clang
cc: clang
cxx: clang++
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install dependencies (Ubuntu)
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y cmake libssl-dev ${{ matrix.cc }} ${{ matrix.cxx }}
- name: Install dependencies (macOS)
if: matrix.os == 'macos-latest'
run: |
brew install cmake gcc@12
- name: Configure CMake
run: |
mkdir -p build
cd build
# Set compiler path
if [ "$RUNNER_OS" = "macOS" ]; then
# For GCC on macOS, use full path
if [ "${{ matrix.compiler }}" = "gcc" ]; then
CC_PATH=$(brew --prefix gcc@12)/bin/gcc-12
CXX_PATH=$(brew --prefix gcc@12)/bin/g++-12
else
CC_PATH=${{ matrix.cc }}
CXX_PATH=${{ matrix.cxx }}
fi
else
CC_PATH=${{ matrix.cc }}
CXX_PATH=${{ matrix.cxx }}
fi
cmake .. \
-DCMAKE_C_COMPILER=${CC_PATH} \
-DCMAKE_CXX_COMPILER=${CXX_PATH} \
-DCMAKE_BUILD_TYPE=Release \
-DAMA_BUILD_SHARED=ON \
-DAMA_BUILD_STATIC=ON \
-DAMA_BUILD_TESTS=ON \
-DAMA_ENABLE_AVX2=ON
- name: Build
run: cmake --build build --config Release -j4
- name: Test
run: cd build && ctest --output-on-failure
# Python Package Build and Test
python-package:
name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ matrix.python-version }}
- name: Install system dependencies (Ubuntu)
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y build-essential python3-dev cmake libssl-dev
- name: Install system dependencies (macOS)
if: matrix.os == 'macos-latest'
run: brew install cmake
- name: Install system dependencies (Windows)
if: matrix.os == 'windows-latest'
shell: pwsh
run: |
# CMake is pre-installed on GitHub-hosted Windows runners.
# Only install via Chocolatey (with retries) if it is missing,
# so a transient Chocolatey CDN outage cannot break the build.
if (Get-Command cmake -ErrorAction SilentlyContinue) {
Write-Host "CMake already available: $(cmake --version | Select-Object -First 1)"
} else {
Write-Host "CMake not found; installing via Chocolatey with retries..."
$maxAttempts = 5
for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) {
Write-Host "Attempt ${attempt}/${maxAttempts}: choco install cmake..."
choco install cmake --installargs 'ADD_CMAKE_TO_PATH=System' -y 2>&1
if ($LASTEXITCODE -eq 0) {
Write-Host "Successfully installed cmake"
break
}
if ($attempt -lt $maxAttempts) {
$delay = $attempt * 15
Write-Host "Install failed, waiting ${delay}s before retry..."
Start-Sleep -Seconds $delay
}
}
if ($LASTEXITCODE -ne 0) {
Write-Error "All ${maxAttempts} attempts to install cmake via Chocolatey failed"
exit 1
}
# Refresh PATH so cmake is available to subsequent steps
Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
refreshenv
}
- name: Install Python build dependencies
run: |
python -m pip install --upgrade pip setuptools wheel
pip install "Cython>=3.0.0" "numpy>=1.24.0,<3.0.0"
# Pre-build the native C library so the install step can rely on
# an already-populated ``build/`` directory. Mirrors the
# proven-working pattern from ``ci.yml::test`` and avoids the
# latent setup.py CMakeBuild fragility on Windows / macOS that
# surfaces as "Python X.Y on {os} -- exit 1/2" without the
# pre-build.
- name: Build native C library (Linux / macOS)
if: matrix.os != 'windows-latest'
run: |
cmake -B build -DAMA_USE_NATIVE_PQC=ON -DCMAKE_BUILD_TYPE=Release -DAMA_BUILD_TESTS=OFF -DAMA_BUILD_EXAMPLES=OFF
# Use Python's os.cpu_count() for portability — `nproc` is GNU
# coreutils and not guaranteed on macos-latest. Bare `-j` would
# unbound make's parallelism and OOM the runner.
cmake --build build -j"$(python -c 'import os; print(os.cpu_count() or 1)')"
- name: Build native C library (Windows)
if: matrix.os == 'windows-latest'
run: |
cmake -B build -DAMA_USE_NATIVE_PQC=ON -DCMAKE_BUILD_TYPE=Release -DAMA_BUILD_TESTS=OFF -DAMA_BUILD_EXAMPLES=OFF
cmake --build build --config Release --parallel
- name: Install Python dependencies + package (with [dev] extras)
# Use the [dev] extras so hypothesis / pytest-cov / mypy / etc.
# come in via the canonical pyproject.toml declaration rather
# than the legacy requirements-dev.txt. Matches ci.yml::test.
run: pip install -e ".[dev]"
- name: Verify module integrity
run: python -m ama_cryptography.integrity --verify
- name: Run tests
env:
# Force backend availability check to fail loudly if the
# native C library didn't build / didn't load — otherwise
# tests silently skip and mask backend regressions.
AMA_CI_REQUIRE_BACKENDS: "1"
# UTF-8 encoding for Windows to support Unicode symbols in output
PYTHONIOENCODING: ${{ matrix.os == 'windows-latest' && 'utf-8' || '' }}
PYTHONUTF8: ${{ matrix.os == 'windows-latest' && '1' || '' }}
run: pytest tests/ -v --tb=short --cov=ama_cryptography --cov-report=xml
- name: Upload coverage
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.xml
flags: python
name: python-${{ matrix.python-version }}
# Linting and Code Quality
lint:
name: Lint and Format Check
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install "black==26.3.1" "ruff>=0.4" "mypy>=1.9,<2"
- name: Lint (ruff)
run: ruff check .
- name: Check formatting (black)
run: black --check --diff .
- name: Type check (mypy --strict)
run: mypy --strict ama_cryptography/ tests/
continue-on-error: false
# Security Audit
security:
name: Security Audit
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pip-audit bandit
- name: Audit dependencies
# CVE-2026-4539: ReDoS in pygments AdlLexer (dev-only, local access, not used at runtime).
# pygments is a transitive dev dependency (via rich/bandit) — see INVARIANT-14.
# Remove ignore when Pygments >2.20.0 ships a fix.
# CVE-2026-3219: pip itself (build-time tool, not a runtime cryptographic
# dependency); no fix version available yet. Remove when upstream ships one.
run: pip-audit --ignore-vuln CVE-2026-4539 --ignore-vuln CVE-2026-3219
- name: Security scan (bandit)
run: bandit -r ama_cryptography/ -l
# Benchmark Regression Detection
# Required job — fails the workflow on detected regression.
# Environment variance on shared CI runners (25-50%) means baselines
# must account for ctypes-fallback throughput, not Cython-optimized peaks.
benchmark-regression:
name: Benchmark Regression Detection
runs-on: ubuntu-latest
timeout-minutes: 15
needs: [c-library]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.11'
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential cmake libssl-dev
- name: Build native C library
run: |
cmake -B build -DAMA_USE_NATIVE_PQC=ON -DCMAKE_BUILD_TYPE=Release -DAMA_BUILD_TESTS=OFF -DAMA_BUILD_EXAMPLES=OFF
cmake --build build -j4
- name: Install Python package
run: |
python -m pip install --upgrade pip
pip install -e .
- name: Run benchmark regression detection
run: |
python benchmarks/benchmark_runner.py \
--verbose \
--baseline benchmarks/baseline.json \
--output benchmarks/regression_results.json \
--markdown benchmarks/regression_report.md
- name: Upload regression report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: benchmark-regression-report
path: |
benchmarks/regression_results.json
benchmarks/regression_report.md
# Docker Build — non-blocking: transient Docker Hub auth/rate-limit
# failures must not gate PRs.
docker:
name: Docker Build
runs-on: ubuntu-latest
timeout-minutes: 20
continue-on-error: true
# Job-level env so the optional-login step can gate on env.* in `if:`.
# `secrets.*` is not allowed in step-level `if:` expressions, which
# otherwise causes a workflow-validation error before any job runs.
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Pre-pull BuildKit image (with retries)
run: |
# Pre-pull the BuildKit image used by setup-buildx-action to
# survive transient Docker Hub outages (504, network timeouts).
IMAGE="moby/buildkit:buildx-stable-1"
for attempt in 1 2 3 4 5; do
echo "Attempt ${attempt}/5: pulling ${IMAGE}..."
if docker pull "${IMAGE}"; then
echo "Successfully pulled ${IMAGE}"
exit 0
fi
echo "Pull failed, waiting $((attempt * 10))s before retry..."
sleep $((attempt * 10))
done
echo "::warning::All 5 pull attempts for ${IMAGE} failed; buildx setup will retry internally"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
- name: Log in to Docker Hub (optional, avoids rate limits)
# Gate on BOTH secrets: a half-configured fork (e.g. username
# set but token missing) would otherwise reach docker/login-action
# and fail noisily, defeating the "optional" intent of the step.
if: env.DOCKERHUB_USERNAME != '' && env.DOCKERHUB_TOKEN != ''
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
username: ${{ env.DOCKERHUB_USERNAME }}
password: ${{ env.DOCKERHUB_TOKEN }}
- name: Build Ubuntu image
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
with:
context: .
file: docker/Dockerfile
push: false
load: true
tags: ama-cryptography:ubuntu
cache-from: type=gha,scope=ubuntu
cache-to: ${{ github.event_name == 'push' && 'type=gha,scope=ubuntu,mode=max' || '' }}
- name: Build Alpine image
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
with:
context: .
file: docker/Dockerfile.alpine
push: false
load: true
tags: ama-cryptography:alpine
cache-from: type=gha,scope=alpine
cache-to: ${{ github.event_name == 'push' && 'type=gha,scope=alpine,mode=max' || '' }}
- name: Test Ubuntu image
run: |
docker run --rm ama-cryptography:ubuntu python3 -c "import ama_cryptography; print('OK')"
- name: Test Alpine image
run: |
docker run --rm ama-cryptography:alpine python3 -c "import ama_cryptography; print('OK')"