name: CI

on:
  pull_request:
  workflow_dispatch:
  push:
    branches:
      - main
    tags:
      - "v*"

permissions:
  contents: read

concurrency:
  group: ci-${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

env:
  CANDIDATE_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
  PIP_DISABLE_PIP_VERSION_CHECK: "1"
  PIP_NO_INPUT: "1"
  UV_VERSION: "0.12.4"

jobs:
  package:
    name: Package
    if: github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v')
    runs-on: ubuntu-latest
    timeout-minutes: 15
    permissions:
      artifact-metadata: write
      attestations: write
      contents: read
      id-token: write
    env:
      BUILD_TOOLS: /tmp/obliteratus-ci-tools

    steps:
      - name: Check out repository
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

      - name: Set up Python
        uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
        with:
          python-version: "3.12"
          cache: pip
          cache-dependency-path: |
            pyproject.toml
            uv.lock

      - name: Install release evidence tooling
        run: |
          python -m pip install "uv==${UV_VERSION}"
          UV_PROJECT_ENVIRONMENT="$BUILD_TOOLS" uv sync --locked --only-group ci

      - name: Create deterministic tested-source snapshot
        run: |
          mkdir -p release package-evidence
          snapshot="release/OBLITERATUS-${GITHUB_SHA}.zip"
          git archive \
            --format=zip \
            --prefix="OBLITERATUS-${GITHUB_SHA}/" \
            --output="$snapshot" \
            "$GITHUB_SHA"
          test "$(find release -maxdepth 1 -type f -name '*.zip' | wc -l)" -eq 1
          unzip -t "$snapshot" | tee package-evidence/source-zip-check.txt

      - name: Generate and bind source CycloneDX SBOM
        run: |
          uv --preview-features sbom-export export \
            --locked \
            --format cyclonedx1.5 \
            --all-extras \
            --no-dev \
            --no-editable \
            --output-file package-evidence/obliteratus.cdx.unbound.json
          mapfile -t snapshots < <(find release -maxdepth 1 -type f -name '*.zip' -print)
          if [ "${#snapshots[@]}" -ne 1 ]; then
            echo "expected exactly one source snapshot, found ${#snapshots[@]}"
            exit 1
          fi
          python scripts/check_supply_chain_policy.py sbom \
            --input package-evidence/obliteratus.cdx.unbound.json \
            --artifact "${snapshots[0]}" \
            --output package-evidence/obliteratus.cdx.json
          rm package-evidence/obliteratus.cdx.unbound.json

      - name: Generate canonical release checksums
        run: >-
          sha256sum release/*.zip package-evidence/obliteratus.cdx.json |
          tee package-evidence/SHA256SUMS

      - name: Attest release-set SLSA provenance
        id: provenance
        uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
        with:
          subject-checksums: package-evidence/SHA256SUMS

      - name: Attest source snapshot SBOM
        id: sbom-attestation
        uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
        with:
          subject-path: release/*.zip
          sbom-path: package-evidence/obliteratus.cdx.json

      - name: Retain Sigstore attestation bundles
        run: |
          cp "${{ steps.provenance.outputs.bundle-path }}" \
            package-evidence/slsa-provenance.sigstore.json
          cp "${{ steps.sbom-attestation.outputs.bundle-path }}" \
            package-evidence/sbom-attestation.sigstore.json
          sha256sum -c package-evidence/SHA256SUMS

      - name: Upload tested source snapshot and release evidence
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: tested-source-snapshot-py3.12
          path: |
            release/
            package-evidence/
          if-no-files-found: error
          retention-days: 14

  lint:
    name: Ruff
    runs-on: ubuntu-latest
    timeout-minutes: 15

    steps:
      - name: Check out repository
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

      - name: Set up Python
        uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
        with:
          python-version: "3.12"
          cache: pip
          cache-dependency-path: |
            pyproject.toml
            uv.lock

      - name: Install Ruff
        run: python -m pip install "ruff==0.16.2"

      - name: Install actionlint with checksum verification
        env:
          ACTIONLINT_VERSION: "1.7.12"
          ACTIONLINT_SHA256: "8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8"
        run: |
          archive="$RUNNER_TEMP/actionlint.tar.gz"
          curl -fsSLo "$archive" \
            "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"
          echo "${ACTIONLINT_SHA256}  ${archive}" | sha256sum -c -
          tar -xzf "$archive" -C "$RUNNER_TEMP" actionlint

      - name: Validate GitHub Actions workflows
        run: |
          "$RUNNER_TEMP/actionlint" -no-color

      - name: Enforce Ruff F gate
        run: >-
          python -m ruff check --select F app.py obliteratus tests scripts

      - name: Report E501 legacy baseline
        if: always()
        run: >-
          python -m ruff check --select E501 --statistics app.py obliteratus tests
          scripts/check_coverage_thresholds.py
          scripts/check_mutation_score.py
          scripts/check_quality_policy.py
          scripts/check_mutation_targets.py
          scripts/prepare_mutation_coverage.py
          scripts/run_prepared_mutmut.py
          scripts/mutmut_coverage_sitecustomize/sitecustomize.py
          scripts/check_conditional_policy.py
          scripts/check_test_risk_map.py
          scripts/conditional_gate_summary.py
          scripts/run_conditional_gate.py
          scripts/run_repeat_gate.py
          scripts/write_test_evidence.py
          scripts/check_supply_chain_policy.py
          scripts/gemma4_12b_recursive_loop.py || true

  arm64-preflight:
    name: Linux ARM64 preflight
    if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main')
    runs-on: ubuntu-24.04-arm
    timeout-minutes: 10
    env:
      CUDA_VISIBLE_DEVICES: ""
      HF_DATASETS_OFFLINE: "1"
      HF_HUB_DISABLE_TELEMETRY: "1"
      HF_HUB_OFFLINE: "1"
      TRANSFORMERS_OFFLINE: "1"
      TEST_ENV: /tmp/obliteratus-arm64-preflight
    steps:
      - name: Check out repository
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
      - name: Set up Python
        uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
        with:
          python-version: "3.12"
          cache: pip
          cache-dependency-path: |
            pyproject.toml
            uv.lock
      - name: Install locked CPU runtime
        run: |
          python -m pip install "uv==${UV_VERSION}"
          UV_PROJECT_ENVIRONMENT="$TEST_ENV" \
            uv sync --locked --no-default-groups --extra dev --no-editable
      - name: Verify ARM64 package and core runtime
        run: |
          "$TEST_ENV/bin/python" -c \
            'import platform; assert platform.machine().lower() in {"aarch64", "arm64"}'
          "$TEST_ENV/bin/python" -m build --wheel
          "$TEST_ENV/bin/python" -c 'import obliteratus; print(obliteratus.__version__)'
          "$TEST_ENV/bin/python" -m obliteratus --help
          "$TEST_ENV/bin/python" -m pytest \
            tests/test_module_imports.py tests/test_device_boundaries.py \
            tests/test_jetson_support_tooling.py -q --no-cov

  pr-core:
    name: Pull request core
    if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main')
    runs-on: ubuntu-latest
    timeout-minutes: 10
    env:
      CUDA_VISIBLE_DEVICES: ""
      HF_DATASETS_OFFLINE: "1"
      HF_HUB_DISABLE_TELEMETRY: "1"
      HF_HUB_OFFLINE: "1"
      TOKENIZERS_PARALLELISM: "false"
      TRANSFORMERS_OFFLINE: "1"
      TEST_ENV: /tmp/obliteratus-pr-test-env

    steps:
      - name: Check out exact candidate
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          fetch-depth: 0
          ref: ${{ env.CANDIDATE_SHA }}

      - name: Set up Python
        uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
        with:
          python-version: "3.12"
          cache: pip
          cache-dependency-path: |
            pyproject.toml
            uv.lock

      - name: Install locked package and test tools
        run: |
          python -m pip install "uv==${UV_VERSION}"
          UV_PROJECT_ENVIRONMENT="$TEST_ENV" \
            uv sync --locked --no-default-groups --extra dev --no-editable

      - name: Resolve exact comparison base
        env:
          EVENT_BASE: ${{ github.event.pull_request.base.sha || github.event.before }}
        run: |
          coverage_base="$EVENT_BASE"
          if ! git rev-parse --verify "${coverage_base}^{commit}" >/dev/null 2>&1; then
            coverage_base="$(git rev-parse HEAD^)"
          fi
          echo "COVERAGE_BASE=$coverage_base" >> "$GITHUB_ENV"

      - name: Validate lock and test policy
        run: |
          uv lock --check
          "$TEST_ENV/bin/python" scripts/check_quality_policy.py \
            --policy ci/test-quality-policy.json
          "$TEST_ENV/bin/python" scripts/check_conditional_policy.py
          "$TEST_ENV/bin/python" scripts/check_test_risk_map.py

      - name: Select and run core and risk-mapped tests
        run: |
          mkdir -p test-results
          "$TEST_ENV/bin/python" scripts/select_pr_tests.py \
            --base-ref "$COVERAGE_BASE" > test-results/selected-tests.txt
          mapfile -t selected_tests < test-results/selected-tests.txt
          if [ "${#selected_tests[@]}" -eq 0 ]; then
            echo "PR test selector returned no tests"
            exit 1
          fi
          printf '%s\n' "${selected_tests[@]}"
          "$TEST_ENV/bin/python" -m pytest \
            "${selected_tests[@]}" \
            -m "not slow and not gpu and not mps and not mlx and not network and not download and not remote and not operator_ui" \
            --cov=app \
            --cov-branch \
            --cov-fail-under=0 \
            --junitxml=test-results/junit-pr-core.xml \
            --cov-report=xml:test-results/coverage-pr-core.xml \
            --cov-report=json:test-results/coverage-pr-core.json

      - name: Enforce pull-request changed-line floor
        run: |
          "$TEST_ENV/bin/python" scripts/check_coverage_thresholds.py \
            test-results/coverage-pr-core.json \
            --min-line 0 \
            --min-branch 0 \
            --min-changed 50 \
            --base-ref "$COVERAGE_BASE"

      - name: Smoke import and CLI
        run: |
          "$TEST_ENV/bin/python" -c 'import obliteratus; print(obliteratus.__version__)'
          "$TEST_ENV/bin/python" -m obliteratus --help

      - name: Build distributions when package inputs change
        run: |
          if git diff --quiet "$COVERAGE_BASE" HEAD -- \
            pyproject.toml uv.lock MANIFEST.in app.py \
            obliteratus/__init__.py obliteratus/__main__.py; then
            echo "package inputs unchanged; release build deferred"
          else
            "$TEST_ENV/bin/python" -m build --sdist --wheel
          fi

      - name: Upload pull-request evidence
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: pr-core-evidence-py3.12
          path: test-results/
          if-no-files-found: error
          retention-days: 30

  test:
    name: Tests py${{ matrix.python-version }}
    if: github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v')
    runs-on: ubuntu-latest
    timeout-minutes: 10
    strategy:
      fail-fast: false
      matrix:
        python-version:
          - "3.10"
          - "3.11"
          - "3.12"
    env:
      CUDA_VISIBLE_DEVICES: ""
      HF_DATASETS_OFFLINE: "1"
      HF_HUB_DISABLE_TELEMETRY: "1"
      HF_HUB_OFFLINE: "1"
      TOKENIZERS_PARALLELISM: "false"
      TRANSFORMERS_OFFLINE: "1"
      TEST_ENV: /tmp/obliteratus-test-env

    steps:
      - name: Check out repository
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          fetch-depth: 0

      - name: Resolve exact release comparison base
        env:
          EVENT_BASE: ${{ github.event.before }}
        run: |
          coverage_base="$EVENT_BASE"
          if ! git rev-parse --verify "${coverage_base}^{commit}" >/dev/null 2>&1; then
            coverage_base="$(git rev-parse HEAD^)"
          fi
          echo "COVERAGE_BASE=$coverage_base" >> "$GITHUB_ENV"

      - name: Set up Python
        uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
        with:
          python-version: ${{ matrix.python-version }}
          cache: pip
          cache-dependency-path: |
            pyproject.toml
            uv.lock

      - name: Install locked package and test tools
        run: |
          python -m pip install "uv==${UV_VERSION}"
          UV_PROJECT_ENVIRONMENT="$TEST_ENV" \
            uv sync --locked --no-default-groups --extra dev --no-editable

      - name: Smoke import and CLI
        run: |
          "$TEST_ENV/bin/python" - <<'PY'
          import obliteratus

          version = getattr(obliteratus, "__version__", None)
          if version is not None:
              print(f"obliteratus version: {version}")
          else:
              print("obliteratus import: ok")
          PY
          "$TEST_ENV/bin/python" -m obliteratus --help

      - name: Run tests with coverage
        run: |
          mkdir -p test-results
          "$TEST_ENV/bin/python" -m pytest \
            -m "not slow and not gpu and not mps and not mlx and not network and not download and not remote and not operator_ui" \
            --cov-branch \
            --cov-fail-under=0 \
            --junitxml="test-results/junit-py${{ matrix.python-version }}.xml" \
            --cov-report="xml:test-results/coverage-py${{ matrix.python-version }}.xml" \
            --cov-report="json:test-results/coverage-py${{ matrix.python-version }}.json"

      - name: Generate exact-base coverage for module regression comparison
        if: matrix.python-version == '3.12'
        env:
          BASE_TEST_ENV: /tmp/obliteratus-base-test-env
          BASE_WORKTREE: /tmp/obliteratus-base-worktree
        run: |
          git rev-parse --verify "${COVERAGE_BASE}^{commit}"
          git worktree add --detach "$BASE_WORKTREE" "$COVERAGE_BASE"
          (
            cd "$BASE_WORKTREE"
            UV_PROJECT_ENVIRONMENT="$BASE_TEST_ENV" \
              uv sync --locked --no-default-groups --extra dev --no-editable
            COVERAGE_FILE="$RUNNER_TEMP/.coverage-base" \
              "$BASE_TEST_ENV/bin/python" -m pytest \
              -m "not slow and not gpu and not mps and not mlx and not network and not download and not remote and not operator_ui" \
              --cov-branch \
              --cov-fail-under=0 \
              --cov-report="json:$GITHUB_WORKSPACE/test-results/base-coverage-py3.12.json"
          ) | tee test-results/base-tests-py3.12.log

      - name: Enforce line and branch coverage floors
        run: |
          module_args=()
          if [ "${{ matrix.python-version }}" = "3.12" ]; then
            module_args=(
              --base-report test-results/base-coverage-py3.12.json
              --touched-module-no-regression
              --new-module-min-line 80
              --new-module-min-branch 75
            )
          fi
          "$TEST_ENV/bin/python" scripts/check_coverage_thresholds.py \
            "test-results/coverage-py${{ matrix.python-version }}.json" \
            --min-line 75 \
            --min-branch 60 \
            --min-file obliteratus/device.py=70 \
            --min-file obliteratus/models/loader.py=70 \
            --min-file obliteratus/architecture_profiles.py=70 \
            --min-file obliteratus/cli.py=70 \
            --min-file obliteratus/mlx_backend.py=70 \
            --min-file obliteratus/evaluation/metrics.py=70 \
            --min-file obliteratus/evaluation/advanced_metrics.py=70 \
            --min-file obliteratus/reporting/report.py=70 \
            --min-file obliteratus/community.py=70 \
            --min-file obliteratus/telemetry.py=70 \
            --min-changed 50 \
            --base-ref "$COVERAGE_BASE" \
            "${module_args[@]}"

      - name: Enforce mature CPU-scope coverage and immutable quality policy
        run: |
          "$TEST_ENV/bin/python" scripts/check_quality_policy.py \
            --policy ci/test-quality-policy.json \
            --coverage "test-results/coverage-py${{ matrix.python-version }}.json"
          "$TEST_ENV/bin/python" scripts/check_conditional_policy.py
          "$TEST_ENV/bin/python" scripts/check_test_risk_map.py

      - name: Write normalized test trend evidence
        if: always()
        run: |
          base_args=()
          if [ -f test-results/base-coverage-py3.12.json ]; then
            base_args=(--base-coverage test-results/base-coverage-py3.12.json)
          fi
          "$TEST_ENV/bin/python" scripts/write_test_evidence.py \
            --coverage "test-results/coverage-py${{ matrix.python-version }}.json" \
            --junit "test-results/junit-py${{ matrix.python-version }}.xml" \
            --head-sha "$CANDIDATE_SHA" \
            --base-sha "$COVERAGE_BASE" \
            --python-version "${{ matrix.python-version }}" \
            --output "test-results/test-trend-py${{ matrix.python-version }}.json" \
            "${base_args[@]}"

      - name: Enforce normalized test duration budgets
        run: |
          "$TEST_ENV/bin/python" scripts/check_quality_policy.py \
            --policy ci/test-quality-policy.json \
            --evidence "test-results/test-trend-py${{ matrix.python-version }}.json"

      - name: Upload test and coverage evidence
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: test-evidence-py${{ matrix.python-version }}
          path: test-results/
          if-no-files-found: error
          retention-days: 90

  checkpoint-windows:
    name: Checkpoint contracts (Windows)
    if: github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v')
    runs-on: windows-latest
    timeout-minutes: 15
    env:
      CUDA_VISIBLE_DEVICES: ""
      HF_DATASETS_OFFLINE: "1"
      HF_HUB_DISABLE_TELEMETRY: "1"
      HF_HUB_OFFLINE: "1"
      TOKENIZERS_PARALLELISM: "false"
      TRANSFORMERS_OFFLINE: "1"

    steps:
      - name: Check out repository
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

      - name: Set up Python
        uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
        with:
          python-version: "3.12"
          cache: pip
          cache-dependency-path: |
            pyproject.toml
            uv.lock

      - name: Install locked test environment
        shell: pwsh
        run: |
          python -m pip install "uv==$env:UV_VERSION"
          $testEnv = Join-Path $env:RUNNER_TEMP "obliteratus-checkpoint-windows"
          $env:UV_PROJECT_ENVIRONMENT = $testEnv
          uv sync --locked --no-default-groups --extra dev --no-editable
          "CHECKPOINT_TEST_ENV=$testEnv" >> $env:GITHUB_ENV

      - name: Run Windows checkpoint contracts
        shell: pwsh
        run: |
          $python = Join-Path $env:CHECKPOINT_TEST_ENV "Scripts/python.exe"
          & $python -m pytest `
            tests/test_checkpoint_atomicity.py `
            tests/test_persistence_contracts.py `
            tests/test_persistence_pipeline.py `
            --no-cov `
            --junitxml="$env:RUNNER_TEMP/checkpoint-windows.xml" `
            -q

      - name: Upload Windows checkpoint evidence
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: checkpoint-evidence-windows-py3.12
          path: ${{ runner.temp }}/checkpoint-windows.xml
          if-no-files-found: error
          retention-days: 90

  quality-depth:
    name: Quality depth
    if: github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v')
    runs-on: ubuntu-latest
    timeout-minutes: 45
    env:
      CUDA_VISIBLE_DEVICES: ""
      HF_DATASETS_OFFLINE: "1"
      HF_HUB_DISABLE_TELEMETRY: "1"
      HF_HUB_OFFLINE: "1"
      TOKENIZERS_PARALLELISM: "false"
      TRANSFORMERS_OFFLINE: "1"
      QUALITY_ENV: /tmp/obliteratus-quality-env

    steps:
      - name: Check out repository
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          fetch-depth: 0

      - name: Resolve exact release comparison base
        env:
          EVENT_BASE: ${{ github.event.before }}
        run: |
          coverage_base="$EVENT_BASE"
          if ! git rev-parse --verify "${coverage_base}^{commit}" >/dev/null 2>&1; then
            coverage_base="$(git rev-parse HEAD^)"
          fi
          echo "COVERAGE_BASE=$coverage_base" >> "$GITHUB_ENV"

      - name: Set up Python
        uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
        with:
          python-version: "3.12"
          cache: pip
          cache-dependency-path: |
            pyproject.toml
            uv.lock

      - name: Install locked quality tooling
        run: |
          python -m pip install "uv==${UV_VERSION}"
          UV_PROJECT_ENVIRONMENT="$QUALITY_ENV" \
            uv sync --locked --extra dev --group quality --no-editable
          mkdir -p quality-evidence

      - name: Run deterministic order and hash-seed repeats
        run: >-
          "$QUALITY_ENV/bin/python" scripts/run_repeat_gate.py
          --output quality-evidence/repeat-gate.json

      - name: Run bounded selective mutation gate
        env:
          BLIS_NUM_THREADS: "1"
          MKL_NUM_THREADS: "1"
          NUMEXPR_NUM_THREADS: "1"
          OMP_THREAD_LIMIT: "1"
          OMP_NUM_THREADS: "1"
          OPENBLAS_NUM_THREADS: "1"
          VECLIB_MAXIMUM_THREADS: "1"
        run: |
          "$QUALITY_ENV/bin/python" scripts/check_mutation_targets.py prepare
          # shellcheck disable=SC2016
          /usr/bin/time -f 'elapsed_seconds=%e\nmax_rss_kb=%M' \
            -o quality-evidence/mutation-time.txt \
            bash -euo pipefail -c '
              "$QUALITY_ENV/bin/python" scripts/prepare_mutation_coverage.py prepare-coverage --max-children 8
              "$QUALITY_ENV/bin/python" scripts/prepare_mutation_coverage.py prepare-stats --max-children 8
              PATH="$QUALITY_ENV/bin:$PATH" \
                "$QUALITY_ENV/bin/python" scripts/run_prepared_mutmut.py run --max-children 8
            '
          "$QUALITY_ENV/bin/python" scripts/check_mutation_targets.py check
          "$QUALITY_ENV/bin/mutmut" results > quality-evidence/mutation-survivors.txt
          "$QUALITY_ENV/bin/mutmut" export-cicd-stats
          cp mutants/mutmut-cicd-stats.json quality-evidence/mutation-stats.json
          cp mutants/mutmut-stats.json quality-evidence/mutation-test-selection.json
          "$QUALITY_ENV/bin/python" scripts/check_mutation_score.py \
            quality-evidence/mutation-stats.json --minimum 85.0

      - name: Write normalized quality trend evidence
        if: always()
        run: |
          evidence_args=()
          if [ -f quality-evidence/repeat-gate.json ]; then
            evidence_args+=(--repeat quality-evidence/repeat-gate.json)
          fi
          if [ -f quality-evidence/mutation-stats.json ]; then
            evidence_args+=(--mutation quality-evidence/mutation-stats.json)
          fi
          "$QUALITY_ENV/bin/python" scripts/write_test_evidence.py \
            --head-sha "$CANDIDATE_SHA" \
            --base-sha "$COVERAGE_BASE" \
            --python-version "3.12" \
            --output quality-evidence/quality-trend-py3.12.json \
            "${evidence_args[@]}"

      - name: Enforce repeat duration budgets
        run: |
          "$QUALITY_ENV/bin/python" scripts/check_quality_policy.py \
            --policy ci/test-quality-policy.json \
            --evidence quality-evidence/quality-trend-py3.12.json

      - name: Upload quality-depth evidence
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: quality-depth-py3.12
          path: quality-evidence/
          if-no-files-found: error
          retention-days: 90

  supply-chain:
    name: Supply chain
    if: github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v')
    needs: package
    runs-on: ubuntu-latest
    timeout-minutes: 30
    env:
      EVIDENCE: /tmp/supply-chain-evidence
      GITLEAKS_SHA256: "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb"
      GITLEAKS_VERSION: "8.30.1"
      RUNTIME_ENV: /tmp/obliteratus-runtime
      SUPPLY_TOOLS: /tmp/obliteratus-supply-tools

    steps:
      - name: Check out repository
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

      - name: Download attested tested-source snapshot
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: tested-source-snapshot-py3.12
          path: /tmp/supply-chain-evidence/release

      - name: Set up Python
        uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
        with:
          python-version: "3.12"
          cache: pip
          cache-dependency-path: |
            pyproject.toml
            uv.lock

      - name: Install locked supply-chain tooling
        run: |
          python -m pip install "uv==${UV_VERSION}"
          uv lock --check
          UV_PROJECT_ENVIRONMENT="$RUNTIME_ENV" \
            uv sync --locked --all-extras --no-default-groups --no-editable
          UV_PROJECT_ENVIRONMENT="$SUPPLY_TOOLS" \
            uv sync --locked --only-group ci
          mkdir -p "$EVIDENCE"
          cd "$RUNNER_TEMP"
          "$RUNTIME_ENV/bin/python" -I - <<'PY' | tee "$EVIDENCE/spaces-import.txt"
          import gradio
          import app

          assert type(app.demo).__name__ == "Blocks"
          print(f"installed all-extras app import: {app.__file__}")
          print(f"gradio: {gradio.__version__}")
          PY

      - name: Validate exception policy
        run: >-
          python scripts/check_supply_chain_policy.py policy
          --policy ci/supply-chain-policy.json

      - name: Scan checkout for secrets with redacted evidence
        run: |
          archive="$RUNNER_TEMP/gitleaks.tar.gz"
          curl -fsSLo "$archive" \
            "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
          echo "${GITLEAKS_SHA256}  ${archive}" | sha256sum -c -
          tar -xzf "$archive" -C "$RUNNER_TEMP" gitleaks
          set +e
          "$RUNNER_TEMP/gitleaks" dir "$GITHUB_WORKSPACE" \
            --redact=100 \
            --report-format json \
            --report-path "$EVIDENCE/gitleaks.json" \
            --no-banner \
            --no-color
          status=$?
          set -e
          echo "$status" > "$EVIDENCE/gitleaks.status"

      - name: Collect vulnerability evidence for supported Python versions
        run: |
          for version in 3.10 3.11 3.12; do
            set +e
            uv --preview-features audit-command,json-output audit \
              --locked \
              --python-version "$version" \
              --python-platform linux \
              --output-format json > "$EVIDENCE/uv-audit-py${version}.json"
            status=$?
            set -e
            echo "$status" > "$EVIDENCE/uv-audit-py${version}.status"
          done

      - name: Verify attested source snapshot and bound CycloneDX SBOM
        run: |
          cd "$EVIDENCE/release"
          sha256sum -c package-evidence/SHA256SUMS
          mapfile -t snapshots < <(find release -maxdepth 1 -type f -name '*.zip' -print)
          if [ "${#snapshots[@]}" -ne 1 ]; then
            echo "expected exactly one source snapshot, found ${#snapshots[@]}"
            exit 1
          fi
          python "$GITHUB_WORKSPACE/scripts/check_supply_chain_policy.py" sbom \
            --input package-evidence/obliteratus.cdx.json \
            --artifact "${snapshots[0]}" \
            --output "$EVIDENCE/verified-obliteratus.cdx.json"

      - name: Collect packaged-dependency license inventory
        run: >-
          "$SUPPLY_TOOLS/bin/pip-licenses"
          --python "$RUNTIME_ENV/bin/python"
          --format json
          --output-file "$EVIDENCE/licenses.json"

      - name: Enforce vulnerability, secret, and license policies
        run: |
          for version in 3.10 3.11 3.12; do
            python scripts/check_supply_chain_policy.py audit \
              --policy ci/supply-chain-policy.json \
              --evidence "$EVIDENCE/uv-audit-py${version}.json" \
              --scanner-status "$EVIDENCE/uv-audit-py${version}.status" \
              --decision "$EVIDENCE/uv-audit-py${version}.decision.json"
          done
          python scripts/check_supply_chain_policy.py secrets \
            --policy ci/supply-chain-policy.json \
            --evidence "$EVIDENCE/gitleaks.json" \
            --scanner-status "$EVIDENCE/gitleaks.status" \
            --decision "$EVIDENCE/gitleaks.decision.json"
          python scripts/check_supply_chain_policy.py licenses \
            --policy ci/supply-chain-policy.json \
            --evidence "$EVIDENCE/licenses.json" \
            --decision "$EVIDENCE/licenses.decision.json"

      - name: Upload supply-chain evidence
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: supply-chain-evidence-py3.12
          path: /tmp/supply-chain-evidence/
          if-no-files-found: error
          retention-days: 14
