name: PR Validation

on:
  pull_request:
    branches:
      - main
    types:
      - opened
      - synchronize
      - reopened

permissions:
  contents: read

jobs:
  # Detect changed paths to gate expensive test jobs
  changes:
    name: Detect Changes
    runs-on: ubuntu-latest
    permissions:
      contents: read
    outputs:
      training: ${{ steps.filter.outputs.training }}
      rl: ${{ steps.filter.outputs.rl }}
      il: ${{ steps.filter.outputs.il }}
      vla: ${{ steps.filter.outputs.vla }}
      evaluation: ${{ steps.filter.outputs.evaluation }}
      osmo_replay: ${{ steps.filter.outputs.osmo_replay }}
      dm_tools: ${{ steps.filter.outputs.dm_tools }}
      data_pipeline: ${{ steps.filter.outputs.data_pipeline }}
      inference: ${{ steps.filter.outputs.inference }}
      gpu_offload: ${{ steps.filter.outputs.gpu_offload }}
      gpu_offload_e2e: ${{ steps.filter.outputs.gpu_offload_e2e }}
      shared_ci: ${{ steps.filter.outputs.shared_ci }}
      dv_backend: ${{ steps.filter.outputs.dv_backend }}
      dv_frontend: ${{ steps.filter.outputs.dv_frontend }}
      fuzz: ${{ steps.filter.outputs.fuzz }}
      docusaurus: ${{ steps.filter.outputs.docusaurus }}
      pester: ${{ steps.filter.outputs.pester }}
      containers: ${{ steps.filter.outputs.containers }}
      selection_status: ${{ steps.filter.outputs.selection_status }}
      base_sha: ${{ steps.filter.outputs.base_sha }}
      head_sha: ${{ steps.filter.outputs.head_sha }}
      file_count: ${{ steps.filter.outputs.file_count }}
    steps:
      - name: Checkout
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1  # v7.0.1
        with:
          fetch-depth: 0
          persist-credentials: false
      - name: Setup Node.js
        uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
        with:
          node-version: '24.14.1'
      - name: Compute path filters
        id: filter
        env:
          BASE_SHA: ${{ github.event.pull_request.base.sha }}
          HEAD_SHA: ${{ github.sha }}
        run: node scripts/ci/select-checks.mjs

  # GPU-free import smoke gate. CPU import smoke runs for every domain on every
  # PR (unconditional baseline); the runtime-image smoke is path-gated to the
  # changed training domain.
  smoke:
    name: Smoke
    needs: changes
    uses: ./.github/workflows/smoke-cpu.yml
    with:
      rl: ${{ needs.changes.outputs.rl == 'true' }}
      il: ${{ needs.changes.outputs.il == 'true' }}
      vla: ${{ needs.changes.outputs.vla == 'true' }}
      evaluation: ${{ needs.changes.outputs.evaluation == 'true' }}
      osmo_replay: ${{ needs.changes.outputs.osmo_replay == 'true' }}
      dataviewer: ${{ needs.changes.outputs.dv_backend == 'true' || needs.changes.outputs.dv_frontend == 'true' }}
    permissions:
      contents: read

  # Validate workflow references, dependency selection, and CI contracts
  workflow-refs-check:
    name: Workflow Refs Check
    uses: ./.github/workflows/workflow-refs-check.yml
    permissions:
      contents: read

  # Spell checking using cspell
  spell-check:
    name: Spell Check
    uses: ./.github/workflows/spell-check.yml
    permissions:
      contents: read

  # Markdown linting using markdownlint-cli2
  markdown-lint:
    name: Markdown Lint
    uses: ./.github/workflows/markdown-lint.yml
    permissions:
      contents: read

  # TOML formatting using Taplo
  toml-format:
    name: TOML Format
    uses: ./.github/workflows/toml-format.yml
    permissions:
      contents: read

  # Markdown table formatting check
  table-format:
    name: Table Format
    uses: ./.github/workflows/table-format.yml
    permissions:
      contents: read

  # Frontmatter validation for markdown files
  frontmatter-validation:
    name: Frontmatter Validation
    uses: ./.github/workflows/frontmatter-validation.yml
    with:
      changed-files-only: false
    permissions:
      contents: read

  # ms.date Freshness Check
  msdate-freshness:
    name: ms.date Freshness Check
    uses: ./.github/workflows/msdate-freshness-check.yml
    with:
      staleness-threshold-days: 90
      changed-files-only: false
      soft-fail: false
    permissions:
      contents: read

  # PowerShell script analysis
  psscriptanalyzer:
    name: PSScriptAnalyzer
    uses: ./.github/workflows/ps-script-analyzer.yml
    with:
      changed-files-only: false
    permissions:
      contents: read

  # YAML/actionlint workflow linting
  yaml-lint:
    name: YAML Lint
    uses: ./.github/workflows/yaml-lint.yml
    with:
      changed-files-only: false
    permissions:
      contents: read

  # Link language locale check
  link-lang-check:
    name: Link Language Check
    uses: ./.github/workflows/link-lang-check.yml
    permissions:
      contents: read

  # Markdown link validation
  markdown-link-check:
    name: Markdown Link Check
    uses: ./.github/workflows/markdown-link-check.yml
    permissions:
      contents: read

  # Dependency review for security vulnerabilities
  dependency-review:
    name: Dependency Review
    uses: ./.github/workflows/dependency-review.yml
    permissions:
      contents: read
      pull-requests: write

  # SHA pinning compliance for GitHub Actions and dependencies
  dependency-pinning:
    name: Dependency Pinning
    uses: ./.github/workflows/dependency-pinning-scan.yml
    permissions:
      contents: read
      security-events: write  # Required for SARIF upload to Security tab
    with:
      upload-sarif: true
      exclude-paths: 'scripts/tests/Fixtures/**,shared/ci/tests/Fixtures/**'

  # OSV-Scanner: advisory-only vulnerability scan across manifests using
  # osv-scanner.toml. The scan step uses continue-on-error so the job always
  # reports success (green) even when vulnerabilities are found; findings still
  # surface in the Security tab via SARIF. This job is intentionally excluded
  # from the pr-validation-summary gate.
  osv-scanner:
    name: OSV-Scanner
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write  # Required for SARIF upload to Security tab
    steps:
      - name: Checkout repository
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1  # v7.0.1
        with:
          persist-credentials: false
      - name: Run OSV-Scanner
        uses: google/osv-scanner-action/osv-scanner-action@a345acffa64b0eaede81a3d9aae6141214d9c8fc  # v2.6.0
        continue-on-error: true
        with:
          # --no-resolve disables transitive resolution; pinned requirements files
          # contain version pins that conflict with transitive constraints, but
          # every pinned package is still scanned directly.
          scan-args: |-
            --config=osv-scanner.toml
            --no-resolve
            --recursive
            ./

  # Container base-image CVE scan (Trivy). Discovers every digest-pinned base
  # image across the repo's Dockerfiles and reports fixable CRITICAL/HIGH
  # vulnerabilities to the Security tab. Runs soft-fail while the shipped base
  # images still carry fixable CVEs (see #1085). Path-gated to Dockerfile, scan
  # workflow, or discovery/slug helper changes; a weekly Container Rescan
  # re-evaluates already-merged base images.
  container-scan:
    name: Container Scan
    needs: changes
    if: needs.changes.outputs.containers == 'true'
    uses: ./.github/workflows/container-scan.yml
    with:
      soft-fail: true
    permissions:
      contents: read
      security-events: write  # Required for SARIF upload to Security tab

  # PowerShell Pester test execution
  pester-tests:
    name: Pester Tests
    needs: changes
    if: needs.changes.outputs.pester == 'true'
    uses: ./.github/workflows/pester-tests.yml
    with:
      code-coverage: true
      changed-files-only: false
      soft-fail: false
    permissions:
      contents: read
      id-token: write

  # Dataviewer frontend lint, type-check, and unit tests
  dataviewer-frontend-tests:
    name: Dataviewer Frontend Tests
    needs: changes
    if: needs.changes.outputs.dv_frontend == 'true'
    uses: ./.github/workflows/dataviewer-frontend-tests.yml
    with:
      code-coverage: true
    permissions:
      contents: read
      id-token: write

  # Docusaurus documentation site tests and build verification
  docusaurus-tests:
    name: Docusaurus Tests
    needs: changes
    if: needs.changes.outputs.docusaurus == 'true'
    uses: ./.github/workflows/docusaurus-tests.yml
    with:
      soft-fail: false
    permissions:
      contents: read

  # Pytest: training component
  pytest-training:
    name: Pytest Training
    needs: changes
    if: needs.changes.outputs.training == 'true'
    uses: ./.github/workflows/pytest-training.yml
    with:
      code-coverage: true
    permissions:
      contents: read
      id-token: write

  # Pytest: data-management/tools component
  pytest-dm-tools:
    name: Pytest DM Tools
    needs: changes
    if: needs.changes.outputs.dm_tools == 'true'
    uses: ./.github/workflows/pytest-dm-tools.yml
    with:
      code-coverage: true
    permissions:
      contents: read
      id-token: write

  # Pytest: data-pipeline component
  pytest-data-pipeline:
    name: Pytest Data Pipeline
    needs: changes
    if: needs.changes.outputs.data_pipeline == 'true'
    uses: ./.github/workflows/pytest-data-pipeline.yml
    with:
      code-coverage: true
    permissions:
      contents: read
      id-token: write

  # Pytest: inference component
  pytest-inference:
    name: Pytest Inference
    needs: changes
    if: needs.changes.outputs.inference == 'true'
    uses: ./.github/workflows/pytest-inference.yml
    with:
      code-coverage: true
    permissions:
      contents: read
      id-token: write

  # Pytest: gpu-offload controller and runtime components
  pytest-gpu-offload:
    name: Pytest GPU Offload
    needs: changes
    if: needs.changes.outputs.gpu_offload == 'true'
    uses: ./.github/workflows/pytest-gpu-offload.yml
    permissions:
      contents: read

  # End-to-end CPU-only smoke of the gpu-offload stack (kind admission +
  # remote-call) for changes under the Helm chart, host scripts, local
  # registry, or examples that pytest-gpu-offload does not exercise.
  gpu-offload-kind-smoke:
    name: GPU Offload Kind Smoke
    needs: changes
    if: needs.changes.outputs.gpu_offload_e2e == 'true'
    uses: ./.github/workflows/gpu-offload-kind-smoke.yml
    permissions:
      contents: read

  # Pytest: shared CI scripts component
  pytest-shared-ci:
    name: Pytest Shared CI
    needs: changes
    if: needs.changes.outputs.shared_ci == 'true'
    uses: ./.github/workflows/pytest-shared-ci.yml
    with:
      code-coverage: true
    permissions:
      contents: read
      id-token: write

  # Dataviewer backend pytest execution
  dataviewer-backend-pytests:
    name: Dataviewer Backend Pytest
    needs: changes
    if: needs.changes.outputs.dv_backend == 'true'
    uses: ./.github/workflows/dataviewer-backend-pytests.yml
    with:
      code-coverage: true
    permissions:
      contents: read
      id-token: write

  # Evaluation domain pytest execution
  evaluation-pytests:
    name: Evaluation Pytest
    uses: ./.github/workflows/evaluation-pytests.yml
    with:
      code-coverage: true
    permissions:
      contents: read
      id-token: write

  # Fuzz regression via deterministic corpus-based tests
  fuzz-regression-tests:
    name: Fuzz Regression Tests
    needs: changes
    if: needs.changes.outputs.fuzz == 'true'
    uses: ./.github/workflows/fuzz-regression-tests.yml
    with:
      code-coverage: true
    permissions:
      contents: read
      id-token: write

  # Python linting using ruff
  python-lint:
    name: Python Lint
    uses: ./.github/workflows/python-lint.yml
    permissions:
      contents: read

  # uv lock/manifest consistency gate across all Python subprojects
  uv-lock-consistency:
    name: uv Lock Consistency
    uses: ./.github/workflows/uv-lock-consistency.yml
    with:
      changed-files-only: false
      soft-fail: false
    permissions:
      contents: read

  # Terraform linting using TFLint
  terraform-lint:
    name: Terraform Lint
    uses: ./.github/workflows/terraform-lint.yml
    with:
      soft-fail: false
    permissions:
      contents: read

  # Terraform format and validate checks
  terraform-validation:
    name: Terraform Validation
    uses: ./.github/workflows/terraform-validation.yml
    with:
      soft-fail: false
      changed-files-only: false
    permissions:
      contents: read

  # Terraform security scanning via Checkov (soft-fail until matrix is fully addressed)
  terraform-security:
    name: Terraform Security
    uses: ./.github/workflows/terraform-security.yml
    with:
      soft-fail: true
      working-directory: infrastructure/terraform
    permissions:
      contents: read
      security-events: write

  # Terraform test execution with Codecov Test Analytics
  terraform-tests:
    name: Terraform Tests
    uses: ./.github/workflows/terraform-tests.yml
    with:
      soft-fail: true
      changed-files-only: false
      code-coverage: true
    permissions:
      contents: read
      id-token: write

  # Go linting using golangci-lint
  go-lint:
    name: Go Lint
    uses: ./.github/workflows/go-lint.yml
    with:
      soft-fail: false
      changed-files-only: false
    permissions:
      contents: read

  # Terraform documentation freshness check
  terraform-docs-check:
    name: Terraform Docs Check
    uses: ./.github/workflows/terraform-docs-check.yml
    with:
      soft-fail: true
      changed-files-only: false
    permissions:
      contents: read

  # Go tests
  go-tests:
    name: Go Tests
    uses: ./.github/workflows/go-tests.yml
    with:
      soft-fail: false
      code-coverage: true
    permissions:
      contents: read
      id-token: write

  # ShellCheck linting for shell scripts
  shellcheck:
    name: ShellCheck
    uses: ./.github/workflows/shellcheck.yml
    with:
      soft-fail: false
      changed-files-only: false
    permissions:
      contents: read

  # CodeQL security analysis
  codeql-analysis:
    name: CodeQL Analysis
    uses: ./.github/workflows/codeql-analysis.yml
    permissions:
      contents: read
      security-events: write
      actions: read

  # Single required-status-check aggregator. Selected mandatory jobs must
  # succeed; only jobs deselected by verified dependency selection may skip.
  pr-validation-summary:
    if: always()
    runs-on: ubuntu-latest
    needs:
      - changes
      - workflow-refs-check
      - spell-check
      - markdown-lint
      - toml-format
      - table-format
      - frontmatter-validation
      - msdate-freshness
      - psscriptanalyzer
      - yaml-lint
      - link-lang-check
      - markdown-link-check
      - dependency-review
      - dependency-pinning
      - pester-tests
      - dataviewer-frontend-tests
      - docusaurus-tests
      - pytest-training
      - pytest-dm-tools
      - pytest-data-pipeline
      - pytest-inference
      - pytest-gpu-offload
      - pytest-shared-ci
      - dataviewer-backend-pytests
      - evaluation-pytests
      - fuzz-regression-tests
      - python-lint
      - uv-lock-consistency
      - terraform-lint
      - terraform-validation
      - terraform-tests
      - go-lint
      - terraform-docs-check
      - go-tests
      - shellcheck
      - codeql-analysis
      - smoke
      - container-scan
      - gpu-offload-kind-smoke
    permissions:
      contents: read
    steps:
      - name: Checkout
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1  # v7.0.1
        with:
          persist-credentials: false
      - name: Setup Node.js
        uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
        with:
          node-version: '24.14.1'
      - name: Evaluate needed job results
        env:
          NEEDS_JSON: ${{ toJSON(needs) }}
        run: node scripts/ci/evaluate-checks.mjs
