Skip to content

build(deps): bump actions/deploy-pages from 4 to 5 #850

build(deps): bump actions/deploy-pages from 4 to 5

build(deps): bump actions/deploy-pages from 4 to 5 #850

Workflow file for this run

name: CI - Tests + Coverage
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
# ─── Shared environment ──────────────────────────────────────────────────────
env:
MEOW_TEST_MODE: "1"
MEOW_PRODUCTION_MODE: "0" # Required alongside MEOW_TEST_MODE to allow export_key() in tests
MEOW_CRYPTO_BACKEND: "rust"
RUST_BACKEND_REQUIRED: "1" # Phase 5: Fail closed if Rust backend unavailable
PIP_DISABLE_PIP_VERSION_CHECK: "1"
PYTHONDONTWRITEBYTECODE: "1"
jobs:
# ═══════════════════════════════════════════════════════════════════════════
# PREFLIGHT: Fast sanity checks β€” lock files, formatting, lint
# Catches the most common failures in <60s before burning CI minutes
# ═══════════════════════════════════════════════════════════════════════════
preflight:
name: "Preflight: Lint + Lock Check"
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.3.0
with:
python-version: "3.12"
cache: "pip"
# -- Lock file verification (catches hash mismatches early) --
- name: Verify lock files are installable
run: |
pip install --require-hashes -r requirements-pip.lock
pip install --dry-run --require-hashes -r requirements.lock 2>&1 | tail -5
pip install --dry-run --require-hashes -r requirements-dev.lock 2>&1 | tail -5
pip install --dry-run --require-hashes -r requirements-ci.lock 2>&1 | tail -5
echo "βœ… All lock files verified"
# -- Install lint tools only --
- name: Install lint tools
run: |
pip install --require-hashes -r requirements-ci.lock
# -- Fast checks that don't need Rust or system deps --
- name: black format check
run: black --check --diff meow_decoder/
- name: flake8 lint
run: flake8 meow_decoder/
- name: mypy type check
run: mypy meow_decoder/ --ignore-missing-imports --no-error-summary
- name: bandit security lint
run: bandit -r meow_decoder/ -ll -q || true
# -- Crypto migration gate (fast grep check) --
- name: Check core crypto files for forbidden imports
run: |
echo "πŸ” Checking core crypto files for forbidden Python crypto imports..."
# Core files that MUST be migrated (fail if they import cryptography/hmac/hashlib)
CORE_FILES="meow_decoder/crypto.py meow_decoder/crypto_enhanced.py meow_decoder/ratchet.py meow_decoder/pq_hybrid.py meow_decoder/forward_secrecy.py"
FORBIDDEN_PATTERN="^[[:space:]]*(from|import)[[:space:]]+(cryptography|hmac|hashlib)"
VIOLATIONS=""
for f in $CORE_FILES; do
if [ -f "$f" ]; then
matches=$(grep -n -E "$FORBIDDEN_PATTERN" "$f" 2>/dev/null || true)
if [ -n "$matches" ]; then
VIOLATIONS="$VIOLATIONS\n$f:\n$matches"
fi
fi
done
if [ -n "$VIOLATIONS" ]; then
echo "❌ Forbidden Python crypto imports found in core files:"
echo -e "$VIOLATIONS"
echo ""
echo "Core crypto files must use the Rust backend (meow_crypto_rs)."
exit 1
fi
echo "βœ… Core crypto files are clean"
# ═══════════════════════════════════════════════════════════════════════════
# GATE 1a/1b: Tests split into two parallel batches to avoid timeouts.
# Batch 1 (a–m): adversarial β†’ metadata_obfuscation
# Batch 2 (m–z): mobile_bridge β†’ zero_key_bytes + security/
# Both must pass. Coverage threshold enforced in batch-1 only.
# ═══════════════════════════════════════════════════════════════════════════
test-batch-1:
name: "Gate 1a: Tests Batch 1 (a-m)"
needs: preflight
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.3.0
with:
python-version: "3.12"
cache: "pip"
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
libzbar0 libgl1 libglib2.0-0 \
build-essential pkg-config libssl-dev \
libpcsclite-dev libudev-dev
- uses: dtolnay/rust-toolchain@d0592fe69e35bc8f12e3dbaf9ad2694d976cb8e3 # stable
with:
toolchain: stable
- name: Install Python dependencies
run: |
pip install --require-hashes -r requirements-pip.lock
pip install --require-hashes -r requirements.lock
pip install --require-hashes -r requirements-dev.lock
pip install --require-hashes -r requirements-ci.lock
- name: Build and install Rust crypto backend
# Local wheel install - can't hash-pin freshly-built artifacts
run: |
cd rust_crypto
maturin build --release --out dist
python -m pip install --force-reinstall --no-deps dist/*.whl
cd ..
- name: Verify Rust backend
run: python -c "import meow_crypto_rs; print('Rust backend OK')"
- name: Rust crypto_core tests
run: |
echo "πŸ¦€ Running Rust crypto_core tests..."
cd crypto_core && cargo test --features "pure-crypto,pq-crypto" --release
echo "βœ… Rust crypto_core tests passed"
- name: Run test suite batch 1 (a-m, no slow markers)
run: |
pytest -m "not slow" \
--override-ini="addopts=" \
--cov=meow_decoder \
--cov-config=.coveragerc \
--cov-report=term-missing \
--cov-fail-under=80 \
-q --no-header \
tests/test_adversarial.py \
tests/test_asymmetric_rekey.py \
tests/test_audit_fixes.py \
tests/test_bridge_protocol.py \
tests/test_cat_errors.py \
tests/test_cat_js_runner.py \
tests/test_cat_utils.py \
tests/test_config.py \
tests/test_constant_time.py \
tests/test_crypto.py \
tests/test_crypto_DEBUG.py \
tests/test_crypto_backend.py \
tests/test_deadmans_switch_cli.py \
tests/test_decode_gif.py \
tests/test_duress_mode.py \
tests/test_e2e_crypto_fountain.py \
tests/test_e2e_gif_ratchet.py \
tests/test_e2e_ratchet_pipeline.py \
tests/test_encode.py \
tests/test_encode_DEBUG.py \
tests/test_fail_closed_enforcement.py \
tests/test_formal_fuzz_gaps.py \
tests/test_fountain.py \
tests/test_fountain_montecarlo.py \
tests/test_frame_mac.py \
tests/test_fuzz_coverage_integration.py \
tests/test_fuzz_targets.py \
tests/test_gif_handler.py \
tests/test_golden_vectors.py \
tests/test_hardware_integration.py \
tests/test_high_security.py \
tests/test_invariants.py \
tests/test_logo_eyes.py \
tests/test_metadata_obfuscation.py
test-batch-2:
name: "Gate 1b: Tests Batch 2 (m-z)"
needs: preflight
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.3.0
with:
python-version: "3.12"
cache: "pip"
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
libzbar0 libgl1 libglib2.0-0 \
build-essential pkg-config libssl-dev \
libpcsclite-dev libudev-dev
- uses: dtolnay/rust-toolchain@d0592fe69e35bc8f12e3dbaf9ad2694d976cb8e3 # stable
with:
toolchain: stable
- name: Install Python dependencies
run: |
pip install --require-hashes -r requirements-pip.lock
pip install --require-hashes -r requirements.lock
pip install --require-hashes -r requirements-dev.lock
pip install --require-hashes -r requirements-ci.lock
- name: Build and install Rust crypto backend
# Local wheel install - can't hash-pin freshly-built artifacts
run: |
cd rust_crypto
maturin build --release --out dist
python -m pip install --force-reinstall --no-deps dist/*.whl
cd ..
- name: Verify Rust backend
run: python -c "import meow_crypto_rs; print('Rust backend OK')"
- name: Run test suite batch 2 (m-z, no slow markers)
run: |
pytest -m "not slow" \
--override-ini="addopts=" \
--cov=meow_decoder \
--cov-config=.coveragerc \
--cov-report=term-missing \
-q --no-header \
tests/test_mobile_bridge.py \
tests/test_no_experimental_imports_in_production.py \
tests/test_no_overclaims.py \
tests/test_no_python_key_bytes.py \
tests/test_phase5_modules.py \
tests/test_pq_crypto_real.py \
tests/test_pq_hybrid.py \
tests/test_pqxdh_upgrade.py \
tests/test_production_boundary.py \
tests/test_production_import_boundary.py \
tests/test_profile_required_and_checked.py \
tests/test_progress.py \
tests/test_property_based.py \
tests/test_property_ratchet_pq.py \
tests/test_property_shamir_dualstream.py \
tests/test_qr_code.py \
tests/test_ratchet.py \
tests/test_rust_crypto_backend.py \
tests/test_security.py \
tests/test_security_hardening.py \
tests/test_security_warnings.py \
tests/test_setup.py \
tests/test_sidechannel.py \
tests/test_signal_invariants.py \
tests/test_stego_advanced.py \
tests/test_stego_adversarial.py \
tests/test_stego_fuzz.py \
tests/test_stego_multilayer.py \
tests/test_stego_phase0.py \
tests/test_stego_phase1.py \
tests/test_tamper_report.py \
tests/test_timelock_duress.py \
tests/test_x25519_forward_secrecy.py \
tests/test_zero_key_bytes.py \
tests/security/
# Python coverage is local-only (terminal report above).
# Codecov monitors Rust coverage via rust-test-coverage.yml.
# ═══════════════════════════════════════════════════════════════════════════
# GATE 2: Cat Mode Golden Video Test (only runs after preflight passes)
# ═══════════════════════════════════════════════════════════════════════════
cat-mode-golden-test:
name: "Gate 2: Cat Mode Golden Video"
needs: preflight
runs-on: ubuntu-latest
timeout-minutes: 10
continue-on-error: true # Allow CI to proceed even if this gate fails
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.3.0
with:
python-version: "3.12"
cache: "pip"
# Check for golden videos FIRST so we don't waste time installing
# chromium + playwright when videos haven't been generated yet.
- name: Check if golden videos exist
id: check_golden
run: |
if [ -f "tests/golden/cat_mode_golden_empty_hash_100ms.webm" ]; then
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "exists=false" >> $GITHUB_OUTPUT
echo "⚠️ Golden videos not found β€” skipping Cat Mode Video tests."
echo " Run 'npm run generate-golden-videos' to create test assets."
fi
- name: Setup Chrome
if: steps.check_golden.outputs.exists == 'true'
uses: browser-actions/setup-chrome@latest # v1.7.1+
with:
chrome-version: stable
- name: Install Python dependencies for test runner
if: steps.check_golden.outputs.exists == 'true'
run: |
pip install selenium webdriver-manager
# Set Chrome binary for webdriver-manager
echo "CHROME_BIN=$(which google-chrome || which chrome)" >> $GITHUB_ENV
- name: Verify golden video checksums
if: steps.check_golden.outputs.exists == 'true'
run: |
cd tests/golden
echo "βœ“ Golden videos present"
- name: Run Cat Mode Golden Video Test
if: steps.check_golden.outputs.exists == 'true'
run: |
cd tests
python3 run_golden_test.py
# ═══════════════════════════════════════════════════════════════════════════
# GATE 3: Cat Mode Error Injection Testing (validates error handling)
# ═══════════════════════════════════════════════════════════════════════════
cat-mode-error-test:
name: "Gate 3: Cat Mode Error Tests"
needs: cat-mode-golden-test
if: always() # Run even if Gate 2 fails
runs-on: ubuntu-latest
timeout-minutes: 20
continue-on-error: true # Allow CI to proceed even if this gate fails
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v4.2.0
with:
node-version: "20"
cache: "npm"
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.3.0
with:
python-version: "3.12"
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y ffmpeg
sudo apt-get install -y chromium-browser || true
- name: Install npm dependencies
run: npm install
- name: Check if error videos exist
id: check_errors
run: |
if [ -d "tests/golden/errors" ] && [ -f "tests/golden/errors/manifest.json" ]; then
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "exists=false" >> $GITHUB_OUTPUT
fi
- name: Generate error videos (if missing)
if: steps.check_errors.outputs.exists == 'false'
run: |
# Generate golden videos first (if they don't exist)
if [ ! -f "tests/golden/cat_mode_golden_empty_hash_100ms.webm" ]; then
echo "πŸ“Ή Generating golden videos first..."
npm run generate-golden-videos
fi
echo "πŸ§ͺ Generating error test videos..."
npm run generate-error-tests
- name: Verify error video checksums
if: steps.check_errors.outputs.exists == 'true'
run: |
cd tests/golden/errors
# Extract checksums from README and verify
# (For now, just check files exist)
if ls *.webm 1> /dev/null 2>&1; then
echo "βœ… Error videos found"
ls -lh *.webm
else
echo "⚠️ No error videos found - regenerating"
cd ../../..
npm run generate-error-tests
fi
- name: Run Error Injection Tests
run: |
cd tests
python3 run_error_tests.py
- name: Upload error diagnostics
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.0
with:
name: error-test-results
path: tests/golden/errors/test_results.json
retention-days: 30
# ═══════════════════════════════════════════════════════════════════════════
# GATE 4: Cross-Browser Testing (validates multi-browser compatibility)
# ═══════════════════════════════════════════════════════════════════════════
cat-mode-cross-browser:
name: "Gate 4: Cross-Browser Tests"
needs: cat-mode-golden-test
if: always() # Run even if Gate 2 fails
runs-on: ubuntu-latest
timeout-minutes: 30
continue-on-error: true # Allow CI to proceed even if this gate fails
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v4.2.0
with:
node-version: "20"
cache: "npm"
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.3.0
with:
python-version: "3.12"
- name: Install npm dependencies
run: npm install
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium firefox webkit
- name: Check if golden videos exist
id: check_golden
run: |
if [ -f "tests/golden/cat_mode_golden_empty_hash_100ms.webm" ]; then
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "exists=false" >> $GITHUB_OUTPUT
fi
- name: Generate golden videos (if missing)
if: steps.check_golden.outputs.exists == 'false'
run: |
sudo apt-get install -y ffmpeg
npm run generate-golden-videos
- name: Run cross-browser tests
run: npm run test:browsers
- name: Upload test artifacts
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.0
with:
name: playwright-report
path: tests/playwright-report/
retention-days: 30
- name: Upload test results
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.0
with:
name: playwright-results
path: tests/playwright-results.json
retention-days: 30
# ═══════════════════════════════════════════════════════════════════════════
# GATE 5: Security coverage (only runs after preflight passes)
# ═══════════════════════════════════════════════════════════════════════════
security-coverage:
name: "Gate 5: Security Coverage"
needs: preflight
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.3.0
with:
python-version: "3.12"
cache: "pip"
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
libzbar0 libgl1 libglib2.0-0 \
build-essential pkg-config libssl-dev \
libpcsclite-dev libudev-dev
- uses: dtolnay/rust-toolchain@d0592fe69e35bc8f12e3dbaf9ad2694d976cb8e3 # stable
with:
toolchain: stable
- name: Install Python + Rust backend
# Local wheel install - can't hash-pin freshly-built artifacts
run: |
pip install --require-hashes -r requirements-pip.lock
pip install --require-hashes -r requirements.lock
pip install --require-hashes -r requirements-dev.lock
pip install --require-hashes -r requirements-ci.lock
cd rust_crypto && maturin build --release --out dist && pip install --force-reinstall --no-deps dist/*.whl && cd ..
- name: Security coverage gate
run: |
pytest tests/ -m "security or crypto or adversarial" \
--override-ini="addopts=" \
--cov --cov-config=.coveragerc-security \
--cov-fail-under=85 \
--cov-report=term-missing \
-q --no-header
# ═══════════════════════════════════════════════════════════════════════════
# GATE 6: Slow Tests (Monte Carlo stress tests for fountain codes)
# Validates statistical guarantees of LT codes - runs 1000s of trials
# ═══════════════════════════════════════════════════════════════════════════
slow-tests:
name: "Gate 6: Slow Tests (Monte Carlo)"
needs: preflight
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.3.0
with:
python-version: "3.12"
cache: "pip"
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
libzbar0 libgl1 libglib2.0-0 \
build-essential pkg-config libssl-dev \
libpcsclite-dev libudev-dev
- uses: dtolnay/rust-toolchain@d0592fe69e35bc8f12e3dbaf9ad2694d976cb8e3 # stable
with:
toolchain: stable
- name: Install Python dependencies
run: |
pip install --require-hashes -r requirements-pip.lock
pip install --require-hashes -r requirements.lock
pip install --require-hashes -r requirements-dev.lock
pip install --require-hashes -r requirements-ci.lock
- name: Build and install Rust crypto backend
run: |
cd rust_crypto
maturin build --release --out dist
python -m pip install --force-reinstall --no-deps dist/*.whl
cd ..
- name: Run slow tests (Monte Carlo fountain stress)
run: |
echo "🎲 Running Monte Carlo fountain code stress tests..."
echo " - 1000 trials at 30% packet loss (β‰₯99.5% success)"
echo " - 1000 trials at 50% packet loss (β‰₯85-99% success)"
echo " - 500 trials at 70% loss (graceful degradation)"
pytest -m slow tests/test_fountain_montecarlo.py \
--override-ini="addopts=" \
-v --tb=short
# ═══════════════════════════════════════════════════════════════════════════
# Required status check β€” preflight + all gates must pass
# ═══════════════════════════════════════════════════════════════════════════
all-gates:
name: "All CI Gates"
needs:
[
preflight,
test-batch-1,
test-batch-2,
cat-mode-golden-test,
cat-mode-error-test,
cat-mode-cross-browser,
security-coverage,
slow-tests,
]
runs-on: ubuntu-latest
timeout-minutes: 5
if: always()
steps:
- name: Check gate results
run: |
FAILED=0
if [[ "${{ needs.preflight.result }}" != "success" ]]; then
echo "❌ Preflight (Lint + Lock Check) failed"
FAILED=1
fi
if [[ "${{ needs.test-batch-1.result }}" != "success" ]]; then
echo "❌ Gate 1a (Tests Batch 1) failed"
FAILED=1
fi
if [[ "${{ needs.test-batch-2.result }}" != "success" ]]; then
echo "❌ Gate 1b (Tests Batch 2) failed"
FAILED=1
fi
# Gates 2-4 are Cat Mode video tests - treat as warnings (continue-on-error)
if [[ "${{ needs.cat-mode-golden-test.result }}" != "success" ]]; then
echo "⚠️ Gate 2 (Cat Mode Golden Video) had issues (non-blocking)"
fi
if [[ "${{ needs.cat-mode-error-test.result }}" != "success" ]]; then
echo "⚠️ Gate 3 (Cat Mode Error Tests) had issues (non-blocking)"
fi
if [[ "${{ needs.cat-mode-cross-browser.result }}" != "success" ]]; then
echo "⚠️ Gate 4 (Cross-Browser Tests) had issues (non-blocking)"
fi
if [[ "${{ needs.security-coverage.result }}" != "success" ]]; then
echo "❌ Gate 5 (Security Coverage) failed"
FAILED=1
fi
if [[ "${{ needs.slow-tests.result }}" != "success" ]]; then
echo "❌ Gate 6 (Slow Tests - Monte Carlo) failed"
FAILED=1
fi
if [[ "$FAILED" == "1" ]]; then
echo "❌ Some critical CI gates failed"
exit 1
fi
echo "βœ… All critical CI gates passed"