name: Conditional tests

on:
  workflow_dispatch:
    inputs:
      candidate_sha:
        description: Candidate commit SHA expected in software conditional evidence
        type: string
        default: ""
      run_model:
        description: Run pinned tiny-model download and evaluation gates
        type: boolean
        default: true
      run_network:
        description: Run disposable network-service boundary gate
        type: boolean
        default: true
      run_ui:
        description: Construct the optional operator UI without a listener
        type: boolean
        default: true
      run_cuda:
        description: Run CUDA and bitsandbytes on the labeled self-hosted runner
        type: boolean
        default: false
      run_jetson:
        description: Run Jetson CUDA on the trusted labeled physical runner
        type: boolean
        default: false
      run_mps:
        description: Run MPS on the labeled Apple Silicon runner
        type: boolean
        default: false
      run_mlx:
        description: Run MLX on the labeled Apple Silicon runner
        type: boolean
        default: false
      run_remote:
        description: Run the least-privileged SSH provider gate
        type: boolean
        default: false
      stale_evidence_reason:
        description: Maintainer reason for accepting older software conditional evidence
        type: string
        default: ""
      stale_evidence_issue:
        description: OBLITERATUS issue URL approving older software conditional evidence
        type: string
        default: ""
      stale_evidence_expires:
        description: ISO date when the stale-evidence waiver expires (maximum 30 days)
        type: string
        default: ""
  schedule:
    - cron: "17 6 * * 0"
  release:
    types: [published]

permissions:
  contents: read

concurrency:
  group: conditional-tests-${{ github.ref }}
  cancel-in-progress: false

env:
  CONDITIONAL_CANDIDATE_SHA: ${{ github.event.inputs.candidate_sha || github.sha }}
  CONDITIONAL_STALE_EVIDENCE_EXPIRES: ${{ github.event.inputs.stale_evidence_expires || '' }}
  CONDITIONAL_STALE_EVIDENCE_ISSUE: ${{ github.event.inputs.stale_evidence_issue || '' }}
  CONDITIONAL_STALE_EVIDENCE_REASON: ${{ github.event.inputs.stale_evidence_reason || '' }}
  PIP_DISABLE_PIP_VERSION_CHECK: "1"
  PIP_NO_INPUT: "1"
  UV_VERSION: "0.12.4"

jobs:
  policy:
    name: Conditional policy
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - name: Check out repository
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
      - name: Validate gate mappings and evidence policy
        run: python3 scripts/check_conditional_policy.py

  model_runtime:
    name: Pinned model runtime and evaluation
    needs: policy
    if: github.event_name != 'workflow_dispatch' || inputs.run_model
    runs-on: ubuntu-latest
    timeout-minutes: 25
    env:
      CONDITIONAL_ENV: /tmp/obliteratus-conditional-model
      HF_HOME: /tmp/obliteratus-hf-cache
      HF_HUB_DISABLE_TELEMETRY: "1"
      TOKENIZERS_PARALLELISM: "false"
    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"
      - name: Restore pinned model cache
        uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5.0.3
        with:
          path: /tmp/obliteratus-hf-cache
          key: hf-tiny-random-gpt2-71034c5-py3.12-${{ runner.os }}
      - name: Install locked runtime
        run: |
          python -m pip install "uv==${UV_VERSION}"
          UV_PROJECT_ENVIRONMENT="$CONDITIONAL_ENV" \
            uv sync --locked --no-default-groups --extra dev --no-editable
      - name: Run model download and cache replay
        run: >-
          "$CONDITIONAL_ENV/bin/python" scripts/run_conditional_gate.py
          model-download-runtime
      - name: Run external evaluation adapter
        run: >-
          "$CONDITIONAL_ENV/bin/python" scripts/run_conditional_gate.py
          external-evaluation
      - name: Validate software evidence freshness
        run: >-
          "$CONDITIONAL_ENV/bin/python" scripts/check_conditional_policy.py
          --candidate-sha "$CONDITIONAL_CANDIDATE_SHA"
          --evidence-dir conditional-evidence
          --require-gate model-download-runtime
          --require-gate external-evaluation
          --stale-evidence-reason "$CONDITIONAL_STALE_EVIDENCE_REASON"
          --stale-evidence-issue "$CONDITIONAL_STALE_EVIDENCE_ISSUE"
          --stale-evidence-expires "$CONDITIONAL_STALE_EVIDENCE_EXPIRES"
      - name: Upload model-runtime evidence
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: conditional-model-runtime-${{ github.run_attempt }}
          path: conditional-evidence/
          if-no-files-found: error
          retention-days: 30

  network_services:
    name: Network service boundary
    needs: policy
    if: github.event_name != 'workflow_dispatch' || inputs.run_network
    runs-on: ubuntu-latest
    timeout-minutes: 10
    env:
      CONDITIONAL_ENV: /tmp/obliteratus-conditional-network
    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"
      - name: Install locked runtime
        run: |
          python -m pip install "uv==${UV_VERSION}"
          UV_PROJECT_ENVIRONMENT="$CONDITIONAL_ENV" \
            uv sync --locked --no-default-groups --extra dev --no-editable
      - name: Run disposable service probe
        run: >-
          "$CONDITIONAL_ENV/bin/python" scripts/run_conditional_gate.py
          network-services
      - name: Validate software evidence freshness
        run: >-
          "$CONDITIONAL_ENV/bin/python" scripts/check_conditional_policy.py
          --candidate-sha "$CONDITIONAL_CANDIDATE_SHA"
          --evidence-dir conditional-evidence
          --require-gate network-services
          --stale-evidence-reason "$CONDITIONAL_STALE_EVIDENCE_REASON"
          --stale-evidence-issue "$CONDITIONAL_STALE_EVIDENCE_ISSUE"
          --stale-evidence-expires "$CONDITIONAL_STALE_EVIDENCE_EXPIRES"
      - name: Upload network evidence
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: conditional-network-${{ github.run_attempt }}
          path: conditional-evidence/
          if-no-files-found: error
          retention-days: 30

  operator_ui:
    name: Operator UI construction
    needs: policy
    if: github.event_name != 'workflow_dispatch' || inputs.run_ui
    runs-on: ubuntu-latest
    timeout-minutes: 15
    env:
      CONDITIONAL_ENV: /tmp/obliteratus-conditional-ui
    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"
      - name: Install locked UI runtime
        run: |
          python -m pip install "uv==${UV_VERSION}"
          UV_PROJECT_ENVIRONMENT="$CONDITIONAL_ENV" \
            uv sync --locked --all-extras --no-default-groups --no-editable
      - name: Construct UI without opening a listener
        run: >-
          "$CONDITIONAL_ENV/bin/python" scripts/run_conditional_gate.py
          operator-ui
      - name: Validate software evidence freshness
        run: >-
          "$CONDITIONAL_ENV/bin/python" scripts/check_conditional_policy.py
          --candidate-sha "$CONDITIONAL_CANDIDATE_SHA"
          --evidence-dir conditional-evidence
          --require-gate operator-ui
          --stale-evidence-reason "$CONDITIONAL_STALE_EVIDENCE_REASON"
          --stale-evidence-issue "$CONDITIONAL_STALE_EVIDENCE_ISSUE"
          --stale-evidence-expires "$CONDITIONAL_STALE_EVIDENCE_EXPIRES"
      - name: Upload UI evidence
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: conditional-operator-ui-${{ github.run_attempt }}
          path: conditional-evidence/
          if-no-files-found: error
          retention-days: 30

  cuda:
    name: CUDA and bitsandbytes runtime
    needs: policy
    if: >-
      (github.event_name == 'workflow_dispatch' && inputs.run_cuda) ||
      (github.event_name != 'workflow_dispatch' && vars.ENABLE_CUDA_GATE == 'true')
    runs-on: [self-hosted, linux, x64, cuda]
    timeout-minutes: 20
    env:
      CONDITIONAL_ENV: /tmp/obliteratus-conditional-cuda
    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"
      - name: Install locked CUDA runtime
        run: |
          python -m pip install "uv==${UV_VERSION}"
          UV_PROJECT_ENVIRONMENT="$CONDITIONAL_ENV" \
            uv sync --locked --no-default-groups --extra dev --extra quantization --no-editable
          CUDA_TORCH_VERSION="$("$CONDITIONAL_ENV/bin/python" -c \
            'import torch; print(torch.__version__.split("+", 1)[0])')"
          UV_TORCH_BACKEND=cu130 uv pip install \
            --python "$CONDITIONAL_ENV/bin/python" \
            --reinstall-package torch \
            "torch==$CUDA_TORCH_VERSION"
          "$CONDITIONAL_ENV/bin/python" -c \
            'import torch; assert torch.version.cuda is not None, torch.__version__'
          uv pip check --python "$CONDITIONAL_ENV/bin/python"
      - name: Run CUDA placement and operation probe
        run: >-
          "$CONDITIONAL_ENV/bin/python" scripts/run_conditional_gate.py cuda-runtime
      - name: Run bitsandbytes quantization probe
        run: >-
          "$CONDITIONAL_ENV/bin/python" scripts/run_conditional_gate.py bitsandbytes-runtime
      - name: Upload CUDA evidence
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: conditional-cuda-${{ github.run_attempt }}
          path: conditional-evidence/
          if-no-files-found: error
          retention-days: 30

  jetson:
    name: NVIDIA Jetson runtime
    needs: policy
    if: github.event_name == 'workflow_dispatch' && inputs.run_jetson
    runs-on: [self-hosted, linux, ARM64, jetson]
    timeout-minutes: 45
    env:
      JETSON_ENV: /tmp/obliteratus-jetson-${{ github.run_id }}-${{ github.run_attempt }}
      JETSON_TOOLS: /tmp/obliteratus-jetson-tools-${{ github.run_id }}-${{ github.run_attempt }}
    steps:
      - name: Check out trusted candidate
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
      - name: Install pinned bootstrap tooling outside the JetPack runtime
        run: |
          python3 -m venv "$JETSON_TOOLS"
          "$JETSON_TOOLS/bin/python" -m pip install "uv==${UV_VERSION}"
      - name: Preserve JetPack PyTorch and install OBLITERATUS
        run: >-
          python3 scripts/setup_jetson.py
          --python python3
          --uv-python "$JETSON_TOOLS/bin/python"
          --venv "$JETSON_ENV"
      - name: Run physical Jetson CUDA probe
        run: >-
          "$JETSON_ENV/bin/python" scripts/run_conditional_gate.py jetson-runtime
      - name: Collect sanitized Jetson evidence
        if: always()
        run: |
          JETSON_PYTHON=python3
          if [ -x "$JETSON_ENV/bin/python" ]; then
            JETSON_PYTHON="$JETSON_ENV/bin/python"
          fi
          "$JETSON_PYTHON" scripts/jetson_support.py \
            --check \
            --gate-evidence conditional-evidence/jetson-runtime.json \
            --output conditional-evidence/jetson-report.json \
            --issue-body conditional-evidence/jetson-issue.md
      - name: Upload Jetson evidence
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: conditional-jetson-${{ github.run_attempt }}
          path: conditional-evidence/
          if-no-files-found: error
          retention-days: 30

  mps:
    name: Apple MPS runtime
    needs: policy
    if: >-
      (github.event_name == 'workflow_dispatch' && inputs.run_mps) ||
      (github.event_name != 'workflow_dispatch' && vars.ENABLE_MPS_GATE == 'true')
    runs-on: [self-hosted, macOS, ARM64, mps]
    timeout-minutes: 15
    env:
      CONDITIONAL_ENV: /tmp/obliteratus-conditional-mps
    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"
      - name: Install locked MPS runtime
        run: |
          python -m pip install "uv==${UV_VERSION}"
          UV_PROJECT_ENVIRONMENT="$CONDITIONAL_ENV" \
            uv sync --locked --no-default-groups --extra dev --no-editable
      - name: Run MPS selection and operation probe
        run: >-
          "$CONDITIONAL_ENV/bin/python" scripts/run_conditional_gate.py mps-runtime
      - name: Upload MPS evidence
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: conditional-mps-${{ github.run_attempt }}
          path: conditional-evidence/
          if-no-files-found: error
          retention-days: 30

  mlx:
    name: Apple MLX runtime
    needs: policy
    if: >-
      (github.event_name == 'workflow_dispatch' && inputs.run_mlx) ||
      (github.event_name != 'workflow_dispatch' && vars.ENABLE_MLX_GATE == 'true')
    runs-on: [self-hosted, macOS, ARM64, mlx]
    timeout-minutes: 20
    env:
      CONDITIONAL_ENV: /tmp/obliteratus-conditional-mlx
    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"
      - name: Install locked MLX runtime
        run: |
          python -m pip install "uv==${UV_VERSION}"
          UV_PROJECT_ENVIRONMENT="$CONDITIONAL_ENV" \
            uv sync --locked --no-default-groups --extra dev --group mlx --no-editable
      - name: Run MLX placement and operation probe
        run: >-
          "$CONDITIONAL_ENV/bin/python" scripts/run_conditional_gate.py mlx-runtime
      - name: Upload MLX evidence
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: conditional-mlx-${{ github.run_attempt }}
          path: conditional-evidence/
          if-no-files-found: error
          retention-days: 30

  remote:
    name: Least-privileged remote provider
    needs: policy
    if: >-
      (github.event_name == 'workflow_dispatch' && inputs.run_remote) ||
      (github.event_name != 'workflow_dispatch' && vars.ENABLE_REMOTE_GATE == 'true')
    runs-on: ubuntu-latest
    timeout-minutes: 10
    env:
      CONDITIONAL_ENV: /tmp/obliteratus-conditional-remote
      OBLITERATUS_REMOTE_HOST: ${{ vars.OBLITERATUS_REMOTE_HOST }}
      OBLITERATUS_REMOTE_USER: ${{ vars.OBLITERATUS_REMOTE_USER }}
      OBLITERATUS_REMOTE_PORT: ${{ vars.OBLITERATUS_REMOTE_PORT }}
    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"
      - name: Install locked runtime
        run: |
          python -m pip install "uv==${UV_VERSION}"
          UV_PROJECT_ENVIRONMENT="$CONDITIONAL_ENV" \
            uv sync --locked --no-default-groups --extra dev --no-editable
      - name: Materialize protected SSH prerequisites
        env:
          REMOTE_KEY_CONTENT: ${{ secrets.OBLITERATUS_REMOTE_KEY }}
          REMOTE_KNOWN_HOSTS_CONTENT: ${{ secrets.OBLITERATUS_REMOTE_KNOWN_HOSTS }}
        run: |
          if [ -z "$OBLITERATUS_REMOTE_HOST" ] || [ -z "$OBLITERATUS_REMOTE_USER" ]; then
            echo "remote gate selected: configure non-root OBLITERATUS_REMOTE_HOST and OBLITERATUS_REMOTE_USER variables"
            exit 1
          fi
          if [ -z "$REMOTE_KEY_CONTENT" ] || [ -z "$REMOTE_KNOWN_HOSTS_CONTENT" ]; then
            echo "remote gate selected: configure OBLITERATUS_REMOTE_KEY and pinned OBLITERATUS_REMOTE_KNOWN_HOSTS secrets"
            exit 1
          fi
          install -m 700 -d "$RUNNER_TEMP/obliteratus-ssh"
          install -m 600 /dev/null "$RUNNER_TEMP/obliteratus-ssh/key"
          install -m 600 /dev/null "$RUNNER_TEMP/obliteratus-ssh/known_hosts"
          printf '%s\n' "$REMOTE_KEY_CONTENT" > "$RUNNER_TEMP/obliteratus-ssh/key"
          printf '%s\n' "$REMOTE_KNOWN_HOSTS_CONTENT" > "$RUNNER_TEMP/obliteratus-ssh/known_hosts"
      - name: Run remote provider probe
        env:
          OBLITERATUS_REMOTE_KEY: ${{ runner.temp }}/obliteratus-ssh/key
          OBLITERATUS_REMOTE_KNOWN_HOSTS: ${{ runner.temp }}/obliteratus-ssh/known_hosts
        run: >-
          "$CONDITIONAL_ENV/bin/python" scripts/run_conditional_gate.py remote-execution
      - name: Upload remote evidence
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: conditional-remote-${{ github.run_attempt }}
          path: conditional-evidence/
          if-no-files-found: warn
          retention-days: 30

  summary:
    name: Conditional result and freshness summary
    if: always()
    needs: [policy, model_runtime, network_services, operator_ui, cuda, jetson, mps, mlx, remote]
    runs-on: ubuntu-latest
    timeout-minutes: 5
    env:
      CONDITIONAL_RESULTS: >-
        {"policy":"${{ needs.policy.result }}","model_runtime":"${{ needs.model_runtime.result }}",
        "network_services":"${{ needs.network_services.result }}","operator_ui":"${{ needs.operator_ui.result }}",
        "cuda":"${{ needs.cuda.result }}","jetson":"${{ needs.jetson.result }}",
        "mps":"${{ needs.mps.result }}","mlx":"${{ needs.mlx.result }}",
        "remote":"${{ needs.remote.result }}"}
      CONDITIONAL_SELECTED: >-
        {"model_runtime":${{ github.event_name != 'workflow_dispatch' || inputs.run_model }},
        "network_services":${{ github.event_name != 'workflow_dispatch' || inputs.run_network }},
        "operator_ui":${{ github.event_name != 'workflow_dispatch' || inputs.run_ui }},
        "cuda":${{ (github.event_name == 'workflow_dispatch' && inputs.run_cuda) || (github.event_name != 'workflow_dispatch' && vars.ENABLE_CUDA_GATE == 'true') }},
        "jetson":${{ github.event_name == 'workflow_dispatch' && inputs.run_jetson }},
        "mps":${{ (github.event_name == 'workflow_dispatch' && inputs.run_mps) || (github.event_name != 'workflow_dispatch' && vars.ENABLE_MPS_GATE == 'true') }},
        "mlx":${{ (github.event_name == 'workflow_dispatch' && inputs.run_mlx) || (github.event_name != 'workflow_dispatch' && vars.ENABLE_MLX_GATE == 'true') }},
        "remote":${{ (github.event_name == 'workflow_dispatch' && inputs.run_remote) || (github.event_name != 'workflow_dispatch' && vars.ENABLE_REMOTE_GATE == 'true') }}}
    steps:
      - name: Check out repository
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
      - name: Enforce selected job results and publish freshness summary
        run: python3 scripts/conditional_gate_summary.py
      - name: Upload conditional summary
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: conditional-summary-${{ github.run_attempt }}
          path: conditional-evidence/summary.json
          if-no-files-found: error
          retention-days: 30
