name: CI/CD

on:
  push:
    branches: [main]
    tags: ["[0-9]*"]
  pull_request:
    branches: [main]

permissions:
  contents: read

env:
  DOCKER_IMAGE: labsai/eddi
  JAVA_VERSION: "25"
  # Pin preflight version + hash — update both when upgrading
  PREFLIGHT_VERSION: "1.17.1"
  PREFLIGHT_SHA256: "15f58d0de7212ac948706515f824d0d2f42b94c11fa85cdb1bc08ad8993226ca"

jobs:
  # ─── Job 0: Detect Changes ──────────────────────────────────────
  detect-changes:
    name: Detect Changes
    runs-on: ubuntu-latest
    outputs:
      code: ${{ steps.filter.outputs.code }}
    steps:
      - name: Checkout
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

      - name: Check paths
        id: filter
        uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3
        with:
          filters: |
            code:
              - 'src/**'
              - 'pom.xml'
              - '.github/workflows/**'
              - 'Dockerfile*'
              - 'docker-compose*.yml'
              - '.dockerignore'
              - 'k8s/**'
              - 'helm/**'
              - 'mvnw*'
              - '.mvn/**'

      - name: Log result
        run: |
          echo "### 📊 Change Detection" >> $GITHUB_STEP_SUMMARY
          echo "- Code changed: **${{ steps.filter.outputs.code }}**" >> $GITHUB_STEP_SUMMARY
          if [ "${{ steps.filter.outputs.code }}" = "false" ]; then
            echo "- ⏭️ Skipping build/test/docker (docs/licenses/config-only change)" >> $GITHUB_STEP_SUMMARY
          fi

  # ─── Job 1: Build & Test ────────────────────────────────────────
  build-and-test:
    name: Build & Test
    runs-on: ubuntu-latest
    needs: detect-changes
    if: needs.detect-changes.outputs.code == 'true'

    steps:
      - name: Checkout
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

      - name: Set up JDK ${{ env.JAVA_VERSION }}
        uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
        with:
          distribution: temurin
          java-version: ${{ env.JAVA_VERSION }}
          cache: maven

      - name: Set execute permission for mvnw
        run: chmod +x mvnw

      - name: Build and test
        run: ./mvnw clean test -B

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
        with:
          name: test-results
          path: target/surefire-reports/
          retention-days: 14

      - name: Upload coverage report
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
        with:
          name: jacoco-report
          path: target/site/jacoco/
          retention-days: 14

      - name: Coverage summary
        if: always()
        run: |
          CSV="target/site/jacoco/jacoco.csv"
          if [ ! -f "$CSV" ]; then
            echo "### ⚠️ No coverage report found" >> $GITHUB_STEP_SUMMARY
            exit 0
          fi

          # Parse JaCoCo CSV columns:
          # $4=INSTRUCTION_MISSED, $5=INSTRUCTION_COVERED,
          # $6=BRANCH_MISSED, $7=BRANCH_COVERED, $8=LINE_MISSED, $9=LINE_COVERED,
          # $12=METHOD_MISSED, $13=METHOD_COVERED
          read IM IC LM LC BM BC MM MC <<< $(awk -F',' 'NR>1 {
            im+=$4; ic+=$5; lm+=$8; lc+=$9; bm+=$6; bc+=$7; mm+=$12; mc+=$13
          } END {
            print im, ic, lm, lc, bm, bc, mm, mc
          }' "$CSV")

          pct() { [ $(($1+$2)) -gt 0 ] && awk "BEGIN {printf \"%.1f\", ($2/($1+$2))*100}" || echo "0.0"; }
          icon() { local p=$(echo "$1" | awk '{printf "%d", $1}'); [ $p -ge 90 ] && echo "🟢" || { [ $p -ge 80 ] && echo "🟡" || echo "🔴"; }; }

          INST_PCT=$(pct $IM $IC)
          LINE_PCT=$(pct $LM $LC)
          BRANCH_PCT=$(pct $BM $BC)
          METHOD_PCT=$(pct $MM $MC)

          # Count tests from surefire reports (match only <testsuite lines)
          TOTAL_TESTS=$(grep -rh '<testsuite ' target/surefire-reports/*.xml 2>/dev/null | sed 's/.*tests="\([0-9]*\)".*/\1/' | awk '{s+=$1} END {print s+0}')

          echo "### 📊 Unit Test Coverage ($TOTAL_TESTS tests)" >> $GITHUB_STEP_SUMMARY
          echo "| Metric | Covered | Total | Percentage | |" >> $GITHUB_STEP_SUMMARY
          echo "|--------|---------|-------|------------|---|" >> $GITHUB_STEP_SUMMARY
          echo "| Instruction | $IC | $((IM+IC)) | **${INST_PCT}%** | $(icon $INST_PCT) |" >> $GITHUB_STEP_SUMMARY
          echo "| Branch | $BC | $((BM+BC)) | **${BRANCH_PCT}%** | $(icon $BRANCH_PCT) |" >> $GITHUB_STEP_SUMMARY
          echo "| Line | $LC | $((LM+LC)) | **${LINE_PCT}%** | $(icon $LINE_PCT) |" >> $GITHUB_STEP_SUMMARY
          echo "| Method | $MC | $((MM+MC)) | **${METHOD_PCT}%** | $(icon $METHOD_PCT) |" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY

          # Top 10 least-covered packages (by instruction coverage)
          echo "<details><summary>📦 Bottom 10 packages by instruction coverage</summary>" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "| Package | Instructions | Coverage |" >> $GITHUB_STEP_SUMMARY
          echo "|---------|-------------|----------|" >> $GITHUB_STEP_SUMMARY
          awk -F',' 'NR>1 {
            pkg=$2; im=$4; ic=$5; total=im+ic
            if (total > 0) { pct=ic/total*100 } else { pct=100 }
            printf "| %s | %d/%d | %.1f%% |\n", pkg, ic, total, pct
          }' "$CSV" | { sort -t'|' -k4 -n 2>/dev/null || true; } | head -10 >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "</details>" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "> ℹ️ Unit tests only. See **Integration Tests** job for merged UT+IT coverage." >> $GITHUB_STEP_SUMMARY

  # ─── Job 2: Integration Tests ─────────────────────────────────
  integration-test:
    name: Integration Tests
    runs-on: ubuntu-latest
    needs: [detect-changes, build-and-test]
    if: needs.detect-changes.outputs.code == 'true'

    steps:
      - name: Checkout
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

      - name: Set up JDK ${{ env.JAVA_VERSION }}
        uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
        with:
          distribution: temurin
          java-version: ${{ env.JAVA_VERSION }}
          cache: maven

      - name: Set execute permission for mvnw
        run: chmod +x mvnw

      - name: Run integration tests
        run: ./mvnw verify -DskipITs=false -Dquarkus.bootstrap.effective-model-builder=true -B
        env:
          MAVEN_OPTS: "--enable-native-access=ALL-UNNAMED"

      - name: Upload integration test results
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
        with:
          name: integration-test-results
          path: target/failsafe-reports/
          retention-days: 14

      - name: Upload integration coverage report
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
        with:
          name: jacoco-it-report
          path: target/site/jacoco-it/
          if-no-files-found: ignore
          retention-days: 14

      - name: Upload merged coverage report
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
        with:
          name: jacoco-merged-report
          path: target/site/jacoco-merged/
          if-no-files-found: ignore
          retention-days: 14

      - name: Coverage summary (UT + IT + Merged)
        if: always()
        run: |
          # Helper functions
          pct() { [ $(($1+$2)) -gt 0 ] && awk "BEGIN {printf \"%.1f\", ($2/($1+$2))*100}" || echo "0.0"; }
          icon() { local p=$(echo "$1" | awk '{printf "%d", $1}'); [ $p -ge 90 ] && echo "🟢" || { [ $p -ge 80 ] && echo "🟡" || echo "🔴"; }; }

          # Parse a JaCoCo CSV and emit a markdown table
          # Columns: $4=INST_MISSED, $5=INST_COVERED, $6=BRANCH_MISSED, $7=BRANCH_COVERED,
          #          $8=LINE_MISSED, $9=LINE_COVERED, $12=METHOD_MISSED, $13=METHOD_COVERED
          emit_table() {
            local csv="$1" title="$2"
            if [ ! -f "$csv" ]; then
              echo "### ⚠️ ${title}: report not found" >> $GITHUB_STEP_SUMMARY
              return
            fi
            read IM IC LM LC BM BC MM MC <<< $(awk -F',' 'NR>1 {
              im+=$4; ic+=$5; lm+=$8; lc+=$9; bm+=$6; bc+=$7; mm+=$12; mc+=$13
            } END {
              print im, ic, lm, lc, bm, bc, mm, mc
            }' "$csv")
            local INST_PCT=$(pct $IM $IC)
            local LINE_PCT=$(pct $LM $LC)
            local BRANCH_PCT=$(pct $BM $BC)
            local METHOD_PCT=$(pct $MM $MC)
            echo "#### ${title}" >> $GITHUB_STEP_SUMMARY
            echo "| Metric | Covered | Total | Percentage | |" >> $GITHUB_STEP_SUMMARY
            echo "|--------|---------|-------|------------|---|" >> $GITHUB_STEP_SUMMARY
            echo "| Instruction | $IC | $((IM+IC)) | **${INST_PCT}%** | $(icon $INST_PCT) |" >> $GITHUB_STEP_SUMMARY
            echo "| Branch | $BC | $((BM+BC)) | **${BRANCH_PCT}%** | $(icon $BRANCH_PCT) |" >> $GITHUB_STEP_SUMMARY
            echo "| Line | $LC | $((LM+LC)) | **${LINE_PCT}%** | $(icon $LINE_PCT) |" >> $GITHUB_STEP_SUMMARY
            echo "| Method | $MC | $((MM+MC)) | **${METHOD_PCT}%** | $(icon $METHOD_PCT) |" >> $GITHUB_STEP_SUMMARY
            echo "" >> $GITHUB_STEP_SUMMARY
          }

          # Count tests
          UT_COUNT=$(grep -rh '<testsuite ' target/surefire-reports/*.xml 2>/dev/null | sed 's/.*tests="\([0-9]*\)".*/\1/' | awk '{s+=$1} END {print s+0}')
          IT_COUNT=$(grep -rh '<testsuite ' target/failsafe-reports/*.xml 2>/dev/null | sed 's/.*tests="\([0-9]*\)".*/\1/' | awk '{s+=$1} END {print s+0}')
          TOTAL=$((UT_COUNT + IT_COUNT))

          echo "### 📊 Test Coverage Report ($TOTAL tests: $UT_COUNT UT + $IT_COUNT IT)" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY

          # Emit all three tables
          emit_table "target/site/jacoco/jacoco.csv" "Unit Tests Only"
          emit_table "target/site/jacoco-it/jacoco.csv" "Integration Tests Only"
          emit_table "target/site/jacoco-merged/jacoco.csv" "✅ Merged (UT + IT)"

          # Bottom 10 packages from merged (or UT-only fallback)
          BEST_CSV="target/site/jacoco-merged/jacoco.csv"
          [ ! -f "$BEST_CSV" ] && BEST_CSV="target/site/jacoco/jacoco.csv"
          if [ -f "$BEST_CSV" ]; then
            echo "<details><summary>📦 Bottom 10 packages by instruction coverage</summary>" >> $GITHUB_STEP_SUMMARY
            echo "" >> $GITHUB_STEP_SUMMARY
            echo "| Package | Instructions | Coverage |" >> $GITHUB_STEP_SUMMARY
            echo "|---------|-------------|----------|" >> $GITHUB_STEP_SUMMARY
            awk -F',' 'NR>1 {
              pkg=$2; im=$4; ic=$5; total=im+ic
              if (total > 0) { pct=ic/total*100 } else { pct=100 }
              printf "| %s | %d/%d | %.1f%% |\n", pkg, ic, total, pct
            }' "$BEST_CSV" | { sort -t'|' -k4 -n 2>/dev/null || true; } | head -10 >> $GITHUB_STEP_SUMMARY
            echo "" >> $GITHUB_STEP_SUMMARY
            echo "</details>" >> $GITHUB_STEP_SUMMARY
          fi

  # ─── Job 2b: CodeQL SAST ───────────────────────────────────────
  codeql:
    name: CodeQL Analysis
    runs-on: ubuntu-latest
    needs: detect-changes
    if: needs.detect-changes.outputs.code == 'true'
    permissions:
      security-events: write
      contents: read
    steps:
      - name: Checkout
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

      - name: Set up JDK ${{ env.JAVA_VERSION }}
        uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
        with:
          distribution: temurin
          java-version: ${{ env.JAVA_VERSION }}
          cache: maven

      - name: Initialize CodeQL
        uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4.36.1
        with:
          languages: java-kotlin
          queries: security-extended

      - name: Set execute permission for mvnw
        run: chmod +x mvnw

      - name: Build for CodeQL
        run: ./mvnw compile -DskipTests -B --no-transfer-progress

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4.36.1
        with:
          category: "/language:java-kotlin"

  # ─── Job 2c: Trivy Filesystem Scan ─────────────────────────────
  trivy-scan:
    name: Trivy Filesystem Scan
    runs-on: ubuntu-latest
    needs: detect-changes
    if: needs.detect-changes.outputs.code == 'true'
    steps:
      - name: Checkout
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

      - name: Run Trivy filesystem scan
        uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
        with:
          scan-type: fs
          scan-ref: .
          severity: CRITICAL,HIGH
          exit-code: 1
          ignore-unfixed: true
          format: table
          trivyignores: .trivyignore

  # ─── Job 2d: Gitleaks Secret Scanning ──────────────────────────
  gitleaks:
    name: Secret Scanning
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          fetch-depth: 0

      - name: Run Gitleaks
        uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2.3.9
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}

  # ─── Job 2e: CycloneDX SBOM Generation ─────────────────────────
  sbom:
    name: Generate SBOM
    runs-on: ubuntu-latest
    needs: build-and-test
    if: github.event_name == 'push'
    steps:
      - name: Checkout
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

      - name: Set up JDK ${{ env.JAVA_VERSION }}
        uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
        with:
          distribution: temurin
          java-version: ${{ env.JAVA_VERSION }}
          cache: maven

      - name: Set execute permission for mvnw
        run: chmod +x mvnw

      - name: Generate CycloneDX SBOM
        run: ./mvnw org.cyclonedx:cyclonedx-maven-plugin:2.9.1:makeBom -DskipTests -B --no-transfer-progress

      - name: Upload SBOM
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
        with:
          name: sbom-cyclonedx
          path: target/bom.json
          retention-days: 90

  # ─── Job 3: Docker Build & Push ─────────────────────────────────
  docker:
    name: Docker Build & Push
    runs-on: ubuntu-latest
    needs: [detect-changes, build-and-test]
    if: >
      always() &&
      github.event_name == 'push' &&
      github.repository == 'labsai/EDDI' &&
      (startsWith(github.ref, 'refs/tags/') ||
       (needs.build-and-test.result == 'success' &&
        !contains(github.event.head_commit.message || '', '[skip docker]')))

    outputs:
      primary-tag: ${{ steps.meta.outputs.primary-tag }}
      is-release: ${{ steps.meta.outputs.is-release }}

    permissions:
      contents: read       # checkout + build
      id-token: write      # Sigstore cosign keyless signing (OIDC → Fulcio)
      attestations: write  # SLSA provenance attestation

    steps:
      - name: Checkout
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

      - name: Set up JDK ${{ env.JAVA_VERSION }}
        uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
        with:
          distribution: temurin
          java-version: ${{ env.JAVA_VERSION }}
          cache: maven

      - name: Set execute permission for mvnw
        run: chmod +x mvnw

      - name: Compute Docker tags
        id: meta
        run: |
          if [[ "$GITHUB_REF" == refs/tags/* ]]; then
            # Tag push: 6.0.0 or 6.0.0-RC2
            PRIMARY_TAG="${GITHUB_REF#refs/tags/}"
            IS_RELEASE="true"

            # Compute semver aliases for stable releases (no pre-release suffix)
            if [[ "$PRIMARY_TAG" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
              MAJOR="${BASH_REMATCH[1]}"
              MINOR="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}"
              echo "major-tag=${MAJOR}" >> $GITHUB_OUTPUT
              echo "minor-tag=${MINOR}" >> $GITHUB_OUTPUT
              IS_STABLE="true"
            else
              IS_STABLE="false"
            fi
            echo "is-stable=${IS_STABLE}" >> $GITHUB_OUTPUT
          else
            # Branch push: version from pom.xml + build number
            POM_VERSION=$(grep -m1 '<version>' pom.xml | sed 's/.*<version>\(.*\)<\/version>.*/\1/')
            PRIMARY_TAG="${POM_VERSION}-b${{ github.run_number }}"
            IS_RELEASE="false"
            echo "is-stable=false" >> $GITHUB_OUTPUT
          fi

          echo "primary-tag=${PRIMARY_TAG}" >> $GITHUB_OUTPUT
          echo "is-release=${IS_RELEASE}" >> $GITHUB_OUTPUT

          echo "### 🐳 Docker Tags" >> $GITHUB_STEP_SUMMARY
          echo "- \`${DOCKER_IMAGE}:${PRIMARY_TAG}\`" >> $GITHUB_STEP_SUMMARY
          if [[ "${IS_STABLE:-false}" == "true" ]]; then
            echo "- \`${DOCKER_IMAGE}:${MINOR}\`" >> $GITHUB_STEP_SUMMARY
            echo "- \`${DOCKER_IMAGE}:${MAJOR}\`" >> $GITHUB_STEP_SUMMARY
          fi
          if [[ "$IS_RELEASE" == "true" ]]; then
            echo "- \`${DOCKER_IMAGE}:latest\`" >> $GITHUB_STEP_SUMMARY
          fi

      - name: Build application
        run: ./mvnw clean package -DskipTests -Plicense-gen -B

      - name: Build Docker image
        run: |
          PRIMARY_TAG="${{ steps.meta.outputs.primary-tag }}"
          IS_RELEASE="${{ steps.meta.outputs.is-release }}"
          IS_STABLE="${{ steps.meta.outputs.is-stable }}"

          TAGS="-t ${DOCKER_IMAGE}:${PRIMARY_TAG}"
          if [[ "$IS_STABLE" == "true" ]]; then
            TAGS="${TAGS} -t ${DOCKER_IMAGE}:${{ steps.meta.outputs.minor-tag }}"
            TAGS="${TAGS} -t ${DOCKER_IMAGE}:${{ steps.meta.outputs.major-tag }}"
          fi
          if [[ "$IS_RELEASE" == "true" ]]; then
            TAGS="${TAGS} -t ${DOCKER_IMAGE}:latest"
          fi

          docker build \
            --build-arg EDDI_VERSION="${PRIMARY_TAG}" \
            --build-arg EDDI_RELEASE="${{ github.run_number }}" \
            -f src/main/docker/Dockerfile \
            ${TAGS} \
            .

      - name: Scan Docker image for vulnerabilities
        # Advisory only — OS-level CVEs in Red Hat's base image are outside our control.
        # The base-image-check.yml workflow monitors for digest updates weekly.
        # Filesystem scan (Job 2c) remains strict for our own dependencies.
        uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
        with:
          scan-type: image
          image-ref: ${{ env.DOCKER_IMAGE }}:${{ steps.meta.outputs.primary-tag }}
          severity: CRITICAL,HIGH
          exit-code: 0
          ignore-unfixed: true
          format: table
          trivyignores: .trivyignore

      - name: Login to Docker Hub
        uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Push to Docker Hub
        id: docker-push
        run: |
          PRIMARY_TAG="${{ steps.meta.outputs.primary-tag }}"
          IS_RELEASE="${{ steps.meta.outputs.is-release }}"
          IS_STABLE="${{ steps.meta.outputs.is-stable }}"

          docker push ${DOCKER_IMAGE}:${PRIMARY_TAG}
          if [[ "$IS_STABLE" == "true" ]]; then
            docker push ${DOCKER_IMAGE}:${{ steps.meta.outputs.minor-tag }}
            docker push ${DOCKER_IMAGE}:${{ steps.meta.outputs.major-tag }}
          fi
          if [[ "$IS_RELEASE" == "true" ]]; then
            docker push ${DOCKER_IMAGE}:latest
          fi

          # Capture the pushed image digest for provenance attestation
          DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' ${DOCKER_IMAGE}:${PRIMARY_TAG} | cut -d'@' -f2)
          echo "digest=${DIGEST}" >> $GITHUB_OUTPUT
          echo "### ✅ Pushed to Docker Hub" >> $GITHUB_STEP_SUMMARY
          echo "Image digest: \`${DIGEST}\`" >> $GITHUB_STEP_SUMMARY

      - name: Install cosign
        uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2

      - name: Sign Docker images (keyless)
        run: |
          PRIMARY_TAG="${{ steps.meta.outputs.primary-tag }}"
          IS_RELEASE="${{ steps.meta.outputs.is-release }}"
          IS_STABLE="${{ steps.meta.outputs.is-stable }}"

          echo "=== Signing ${DOCKER_IMAGE}:${PRIMARY_TAG} ==="
          cosign sign --yes ${DOCKER_IMAGE}:${PRIMARY_TAG}

          if [[ "$IS_STABLE" == "true" ]]; then
            echo "=== Signing semver aliases ==="
            cosign sign --yes ${DOCKER_IMAGE}:${{ steps.meta.outputs.minor-tag }}
            cosign sign --yes ${DOCKER_IMAGE}:${{ steps.meta.outputs.major-tag }}
          fi

          if [[ "$IS_RELEASE" == "true" ]]; then
            echo "=== Signing latest ==="
            cosign sign --yes ${DOCKER_IMAGE}:latest
          fi

          echo "### 🔏 Docker Images Signed (Sigstore cosign)" >> $GITHUB_STEP_SUMMARY
          echo "Keyless OIDC signing via GitHub Actions identity." >> $GITHUB_STEP_SUMMARY
          echo "Verify: \`cosign verify --certificate-oidc-issuer https://token.actions.githubusercontent.com --certificate-identity-regexp '^https://github\\.com/labsai/EDDI/\\.github/workflows/ci\\.yml@refs/(heads/main|tags/.+)$' ${DOCKER_IMAGE}:${PRIMARY_TAG}\`" >> $GITHUB_STEP_SUMMARY

      - name: Generate SLSA provenance attestation
        if: steps.docker-push.outputs.digest != ''
        uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
        with:
          subject-name: docker.io/${{ env.DOCKER_IMAGE }}
          subject-digest: ${{ steps.docker-push.outputs.digest }}
          push-to-registry: true


  # ─── Job 3b: GitHub Release (tag pushes only) ──────────────────
  release:
    name: GitHub Release
    runs-on: ubuntu-latest
    needs: docker
    if: startsWith(github.ref, 'refs/tags/')
    permissions:
      contents: write  # Required for creating GitHub Releases
    steps:
      - name: Create GitHub Release
        uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
        with:
          generate_release_notes: true
          make_latest: true
          body: |
            ## 🐳 Docker Image

            This release is distributed as a Docker image:

            ```bash
            docker pull ${{ env.DOCKER_IMAGE }}:${{ needs.docker.outputs.primary-tag }}
            ```

            ### Verify image signature

            ```bash
            cosign verify \
              --certificate-oidc-issuer https://token.actions.githubusercontent.com \
              --certificate-identity-regexp '^https://github\.com/labsai/EDDI/\.github/workflows/ci\.yml@refs/(heads/main|tags/.+)$' \
              ${{ env.DOCKER_IMAGE }}:${{ needs.docker.outputs.primary-tag }}
            ```

            > **Note:** EDDI is distributed exclusively as a Docker image. There are no binary downloads.
            > See the [Docker Hub page](https://hub.docker.com/r/labsai/eddi) for all available tags.

  # ─── Job 4: Smoke Test ──────────────────────────────────────────
  smoke-test:
    name: Smoke Test
    runs-on: ubuntu-latest
    needs: docker

    steps:
      - name: Install cosign
        uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2

      - name: Verify Docker image signature
        run: |
          PRIMARY_TAG="${{ needs.docker.outputs.primary-tag }}"
          echo "=== Verifying signature for ${DOCKER_IMAGE}:${PRIMARY_TAG} ==="
          cosign verify \
            --certificate-oidc-issuer https://token.actions.githubusercontent.com \
            --certificate-identity-regexp '^https://github\.com/labsai/EDDI/\.github/workflows/ci\.yml@refs/(heads/main|tags/.+)$' \
            ${DOCKER_IMAGE}:${PRIMARY_TAG}
          echo "✅ Image signature verified"
          echo "### 🔏 Image Signature Verified" >> $GITHUB_STEP_SUMMARY

      - name: Start MongoDB
        run: |
          docker run -d --name mongodb \
            --network host \
            mongo:6.0
          echo "Waiting for MongoDB..."
          for i in $(seq 1 15); do
            if docker exec mongodb mongosh --eval "db.runCommand({ping:1})" > /dev/null 2>&1; then
              echo "✅ MongoDB ready"
              break
            fi
            sleep 1
          done

      - name: Start EDDI
        run: |
          PRIMARY_TAG="${{ needs.docker.outputs.primary-tag }}"
          echo "Starting ${DOCKER_IMAGE}:${PRIMARY_TAG}..."

          docker run -d --name eddi \
            --network host \
            -e "MONGODB_CONNECTIONSTRING=mongodb://localhost:27017/eddi?retryWrites=true&w=majority&connectTimeoutMS=10000&socketTimeoutMS=30000" \
            -e "EDDI_SECURITY_ALLOW_UNAUTHENTICATED=true" \
            ${DOCKER_IMAGE}:${PRIMARY_TAG}

      - name: Wait for healthy
        run: |
          echo "Waiting for EDDI to start..."
          for i in $(seq 1 30); do
            if curl -sf http://localhost:7070/q/health/ready > /dev/null 2>&1; then
              echo "✅ EDDI is healthy after $((i * 2))s"
              exit 0
            fi
            sleep 2
          done
          echo "❌ EDDI failed to start within 60s"
          echo "=== EDDI logs ==="
          docker logs eddi
          echo "=== MongoDB logs ==="
          docker logs mongodb
          exit 1

      - name: Verify API responds
        run: |
          echo "=== Health endpoint ==="
          HEALTH_JSON=$(curl -sf http://localhost:7070/q/health/ready)
          echo "$HEALTH_JSON" | python3 -m json.tool

          echo ""
          echo "=== OpenAPI spec available ==="
          HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" http://localhost:7070/openapi)
          echo "HTTP ${HTTP_CODE}"
          if [ "$HTTP_CODE" != "200" ]; then
            echo "❌ OpenAPI endpoint returned ${HTTP_CODE}"
            exit 1
          fi

      - name: Verify security headers
        run: |
          echo "=== Security Headers Check ==="
          HEADERS=$(curl -sI http://localhost:7070/q/health/ready)
          echo "$HEADERS"
          echo ""

          MISSING=0
          check_header() {
            if echo "$HEADERS" | grep -qi "$1"; then
              echo "✅ $1 present"
            else
              echo "⚠️  $1 MISSING"
              MISSING=1
            fi
          }

          check_header "X-Content-Type-Options"
          check_header "X-Frame-Options"
          check_header "Content-Security-Policy"

          echo ""
          if [ "$MISSING" -eq 1 ]; then
            echo "### ⚠️ Security Headers" >> $GITHUB_STEP_SUMMARY
            echo "Some recommended security headers are missing — see job output for details." >> $GITHUB_STEP_SUMMARY
            echo "::warning::Recommended security headers missing — review smoke-test output"
          else
            echo "### ✅ Security Headers" >> $GITHUB_STEP_SUMMARY
            echo "All checked security headers are present." >> $GITHUB_STEP_SUMMARY
          fi

      - name: Cleanup
        if: always()
        run: |
          docker rm -f eddi 2>/dev/null || true
          docker rm -f mongodb 2>/dev/null || true

  # ─── Job 4b: ZAP API Scan (DAST) ────────────────────────────────
  zap-scan:
    name: ZAP API Scan
    runs-on: ubuntu-latest
    needs: [smoke-test, docker]
    # Only run on pushes (smoke-test already has the running container infrastructure)
    if: github.event_name == 'push'

    steps:
      - name: Checkout
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

      - name: Start MongoDB
        run: |
          docker run -d --name mongodb \
            --network host \
            mongo:6.0
          echo "Waiting for MongoDB..."
          for i in $(seq 1 15); do
            if docker exec mongodb mongosh --eval "db.runCommand({ping:1})" > /dev/null 2>&1; then
              echo "✅ MongoDB ready"
              break
            fi
            sleep 1
          done

      - name: Start EDDI
        run: |
          PRIMARY_TAG="${{ needs.docker.outputs.primary-tag }}"
          if [ -z "$PRIMARY_TAG" ]; then
            POM_VERSION=$(grep -m1 '<version>' pom.xml | sed 's/.*<version>\(.*\)<\/version>.*/\1/')
            PRIMARY_TAG="${POM_VERSION}-b${{ github.run_number }}"
          fi
          echo "Starting ${DOCKER_IMAGE}:${PRIMARY_TAG}..."

          docker pull ${DOCKER_IMAGE}:${PRIMARY_TAG} || true
          docker run -d --name eddi-zap \
            --network host \
            -e "MONGODB_CONNECTIONSTRING=mongodb://localhost:27017/eddi-zap?retryWrites=true&w=majority&connectTimeoutMS=10000&socketTimeoutMS=30000" \
            -e "EDDI_SECURITY_ALLOW_UNAUTHENTICATED=true" \
            ${DOCKER_IMAGE}:${PRIMARY_TAG}

      - name: Wait for healthy
        run: |
          echo "Waiting for EDDI to start..."
          for i in $(seq 1 30); do
            if curl -sf http://localhost:7070/q/health/ready > /dev/null 2>&1; then
              echo "✅ EDDI is healthy after $((i * 2))s"
              exit 0
            fi
            sleep 2
          done
          echo "❌ EDDI failed to start within 60s"
          docker logs eddi-zap
          exit 1

      - name: ZAP API Scan (passive)
        uses: zaproxy/action-api-scan@5158fe4d9d8fcc75ea204db81317cce7f9e5453d # v0.10.0
        with:
          target: http://localhost:7070/openapi
          format: openapi
          fail_action: false  # Report-only — promote to true after tuning false positives
          # -S  = passive-only (skip active fuzzing — CodeQL SAST + Trivy already cover injection/CVE)
          # -T 2 = cap spider at 2 min for deterministic CI time
          # -a  = include alpha passive rules (headers, cookies, CORS, info disclosure)
          cmd_options: '-S -T 2 -a -z "-config api.disablekey=true"'
          allow_issue_writing: false

      - name: Upload ZAP report
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
        with:
          name: zap-report
          path: report_json.json
          if-no-files-found: ignore
          retention-days: 14

      - name: Cleanup
        if: always()
        run: |
          docker rm -f eddi-zap 2>/dev/null || true
          docker rm -f mongodb 2>/dev/null || true

  # ─── Job 5: Red Hat Preflight — PR Dry-Run ──────────────────────
  preflight-check:
    name: Preflight Dry-Run (PR)
    runs-on: ubuntu-latest
    needs: build-and-test
    if: github.event_name == 'pull_request'

    steps:
      - name: Checkout
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

      - name: Set up JDK ${{ env.JAVA_VERSION }}
        uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
        with:
          distribution: temurin
          java-version: ${{ env.JAVA_VERSION }}
          cache: maven

      - name: Set execute permission for mvnw
        run: chmod +x mvnw

      - name: Build application
        run: ./mvnw clean package -DskipTests -Plicense-gen -B

      - name: Build Docker image with Red Hat labels
        run: |
          docker build \
            -f src/main/docker/Dockerfile \
            -t eddi-preflight-check:test \
            .

      - name: Verify Red Hat labels present
        run: |
          echo "=== Checking required Red Hat labels ==="
          LABELS=$(docker inspect eddi-preflight-check:test --format '{{json .Config.Labels}}')
          echo "$LABELS" | python3 -m json.tool
          MISSING=0
          for label in name vendor version release summary description; do
            if echo "$LABELS" | python3 -c "import sys,json; d=json.load(sys.stdin); assert '$label' in d" 2>/dev/null; then
              echo "✅ Label '$label' present"
            else
              echo "❌ Label '$label' MISSING"
              MISSING=1
            fi
          done
          if [ "$MISSING" -eq 1 ]; then
            echo "::error::Required Red Hat labels are missing"
            exit 1
          fi

      - name: Verify /licenses in container
        run: |
          echo "=== /licenses directory ==="
          docker run --rm --entrypoint="" eddi-preflight-check:test ls -la /licenses/
          echo ""
          echo "=== THIRD-PARTY.txt exists ==="
          docker run --rm --entrypoint="" eddi-preflight-check:test \
            test -f /licenses/THIRD-PARTY.txt && echo "✅ THIRD-PARTY.txt found" || { echo "❌ THIRD-PARTY.txt missing"; exit 1; }

      - name: Install preflight
        run: |
          echo "Installing preflight ${PREFLIGHT_VERSION}..."
          curl -sL "https://github.com/redhat-openshift-ecosystem/openshift-preflight/releases/download/${PREFLIGHT_VERSION}/preflight-linux-amd64" \
            -o /usr/local/bin/preflight
          echo "${PREFLIGHT_SHA256}  /usr/local/bin/preflight" | sha256sum -c -
          chmod +x /usr/local/bin/preflight
          preflight --version

      - name: Preflight check (local, no submit)
        run: |
          preflight check container \
            eddi-preflight-check:test \
            2>&1 | tee preflight-dryrun.txt || true
          echo ""
          echo "=== Preflight Summary ==="
          if grep -q "FAILED" preflight-dryrun.txt; then
            echo "::warning::Preflight check has failures — review output above."
            echo "Note: Some checks (e.g. HasUniqueTag) require a pushed image and are expected to fail in dry-run."
          else
            echo "✅ All preflight checks passed"
          fi

      - name: Upload preflight dry-run results
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
        with:
          name: preflight-dryrun
          path: preflight-dryrun.txt
          retention-days: 14

  # ─── Job 6: Red Hat Preflight — Push Verification ───────────────
  preflight-push:
    name: Preflight Verify (Pushed Image)
    runs-on: ubuntu-latest
    needs: docker
    if: >
      github.event_name == 'push' &&
      needs.docker.result == 'success'

    steps:
      - name: Login to Docker Hub
        uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Pull pushed image
        run: |
          PRIMARY_TAG="${{ needs.docker.outputs.primary-tag }}"
          echo "Pulling ${DOCKER_IMAGE}:${PRIMARY_TAG}..."
          docker pull ${DOCKER_IMAGE}:${PRIMARY_TAG}

      - name: Verify Red Hat labels present
        run: |
          PRIMARY_TAG="${{ needs.docker.outputs.primary-tag }}"
          echo "=== Checking required Red Hat labels ==="
          LABELS=$(docker inspect ${DOCKER_IMAGE}:${PRIMARY_TAG} --format '{{json .Config.Labels}}')
          echo "$LABELS" | python3 -m json.tool
          MISSING=0
          for label in name vendor version release summary description; do
            if echo "$LABELS" | python3 -c "import sys,json; d=json.load(sys.stdin); assert '$label' in d" 2>/dev/null; then
              echo "✅ Label '$label' present"
            else
              echo "❌ Label '$label' MISSING"
              MISSING=1
            fi
          done
          if [ "$MISSING" -eq 1 ]; then
            echo "::error::Required Red Hat labels are missing on pushed image"
            exit 1
          fi

      - name: Verify /licenses in container
        run: |
          PRIMARY_TAG="${{ needs.docker.outputs.primary-tag }}"
          echo "=== /licenses directory ==="
          docker run --rm --entrypoint="" ${DOCKER_IMAGE}:${PRIMARY_TAG} ls -la /licenses/
          echo ""
          echo "=== THIRD-PARTY.txt exists ==="
          docker run --rm --entrypoint="" ${DOCKER_IMAGE}:${PRIMARY_TAG} \
            test -f /licenses/THIRD-PARTY.txt && echo "✅ THIRD-PARTY.txt found" || { echo "❌ THIRD-PARTY.txt missing"; exit 1; }

      - name: Install preflight
        run: |
          echo "Installing preflight ${PREFLIGHT_VERSION}..."
          curl -sL "https://github.com/redhat-openshift-ecosystem/openshift-preflight/releases/download/${PREFLIGHT_VERSION}/preflight-linux-amd64" \
            -o /usr/local/bin/preflight
          echo "${PREFLIGHT_SHA256}  /usr/local/bin/preflight" | sha256sum -c -
          chmod +x /usr/local/bin/preflight
          preflight --version

      - name: Preflight check (pushed image)
        env:
          REDHAT_API_TOKEN: ${{ secrets.REDHAT_API_TOKEN }}
          REDHAT_CERT_PROJECT_ID: ${{ secrets.REDHAT_CERT_PROJECT_ID }}
        run: |
          PRIMARY_TAG="${{ needs.docker.outputs.primary-tag }}"
          IS_RELEASE="${{ needs.docker.outputs.is-release }}"

          # On release tags, submit results to Red Hat Partner Connect
          SUBMIT_FLAG=""
          if [ "$IS_RELEASE" = "true" ]; then
            if [ -n "$REDHAT_API_TOKEN" ] && [ -n "$REDHAT_CERT_PROJECT_ID" ]; then
              # Pass credentials via preflight's native env vars (avoids process-listing exposure)
              export PFLT_PYXIS_API_TOKEN="$REDHAT_API_TOKEN"
              export PFLT_CERTIFICATION_COMPONENT_ID="$REDHAT_CERT_PROJECT_ID"
              SUBMIT_FLAG="--submit"
              echo "📤 Release tag detected — submitting to Red Hat Partner Connect"
            else
              echo "::warning::Release tag detected but REDHAT_API_TOKEN or REDHAT_CERT_PROJECT_ID secret is missing — running dry-run only"
            fi
          else
            echo "🔍 Non-release push — running preflight in dry-run mode (no submit)"
          fi

          preflight check container \
            docker.io/${DOCKER_IMAGE}:${PRIMARY_TAG} \
            --docker-config=${HOME}/.docker/config.json \
            ${SUBMIT_FLAG} \
            2>&1 | tee preflight-push-results.txt
          PREFLIGHT_EXIT=${PIPESTATUS[0]}
          echo ""
          echo "=== Preflight Summary ==="
          if [ "$PREFLIGHT_EXIT" -ne 0 ] || grep -q "FAILED" preflight-push-results.txt; then
            echo "::error::Preflight certification check FAILED on pushed image (exit code: ${PREFLIGHT_EXIT})"
            echo "The container will not pass Red Hat certification in its current state."
            exit 1
          else
            echo "✅ All preflight checks passed on pushed image"
            if [ -n "$SUBMIT_FLAG" ]; then
              echo "📤 Results submitted to Red Hat Partner Connect"
            fi
          fi

      - name: Upload preflight results
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
        with:
          name: preflight-push-results
          path: |
            preflight-push-results.txt
            artifacts/
          retention-days: 30

  # ─── Job 7: Slack Notification ─────────────────────────────────
  notify-slack:
    name: Slack Notification
    runs-on: ubuntu-latest
    if: always()
    needs:
      [detect-changes, build-and-test, integration-test, docker, smoke-test, zap-scan, trivy-scan, gitleaks, preflight-check, preflight-push]

    steps:
      - name: Send build notification
        env:
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
          COMMIT_MSG_RAW: ${{ github.event.head_commit.message || github.event.pull_request.title || 'Manual run' }}
          GH_ACTOR: ${{ github.actor }}
          RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
          RUN_NUMBER: ${{ github.run_number }}
        run: |
          # Skip if no webhook configured
          if [ -z "$SLACK_WEBHOOK" ]; then
            echo "No SLACK_WEBHOOK_URL configured, skipping"
            exit 0
          fi

          # Skip notification for docs-only changes (all jobs skipped) — but always notify on tags
          if [ "${{ needs.detect-changes.outputs.code }}" = "false" ] && [[ "$GITHUB_REF" != refs/tags/* ]]; then
            echo "### ⏭️ Docs-only change — no build notification sent" >> $GITHUB_STEP_SUMMARY
            exit 0
          fi

          # Determine overall status
          BUILD="${{ needs.build-and-test.result }}"
          IT="${{ needs.integration-test.result }}"
          DOCKER="${{ needs.docker.result }}"
          SMOKE="${{ needs.smoke-test.result }}"
          ZAP="${{ needs.zap-scan.result }}"
          GITLEAKS="${{ needs.gitleaks.result }}"
          TRIVY="${{ needs.trivy-scan.result }}"
          PREFLIGHT_PR="${{ needs.preflight-check.result }}"
          PREFLIGHT_PUSH="${{ needs.preflight-push.result }}"
          # Merge: whichever preflight job actually ran wins (the other is "skipped")
          if [ "$PREFLIGHT_PUSH" != "skipped" ]; then
            PREFLIGHT="$PREFLIGHT_PUSH"
          else
            PREFLIGHT="$PREFLIGHT_PR"
          fi

          # Map result to emoji
          status_icon() {
            case "$1" in
              success)  echo "✅" ;;
              failure)  echo "❌" ;;
              skipped)  echo "⏭️" ;;
              cancelled) echo "🚫" ;;
              *)        echo "❓" ;;
            esac
          }

          # On tag pushes, build-and-test may be skipped (detect-changes bypass) — that's OK
          # because Docker builds from source independently
          if [[ "$GITHUB_REF" == refs/tags/* ]]; then
            [ "$BUILD" = "skipped" ] && BUILD="success"
            [ "$IT" = "skipped" ] && IT="success"
          fi

          # Overall status
          if [ "$BUILD" = "success" ] && [ "$IT" != "failure" ] && [ "$DOCKER" != "failure" ] && [ "$SMOKE" != "failure" ] && [ "$GITLEAKS" != "failure" ] && [ "$TRIVY" != "failure" ]; then
            OVERALL_ICON="✅"
            OVERALL_TEXT="Build Passed"
          else
            OVERALL_ICON="❌"
            OVERALL_TEXT="Build Failed"
          fi

          # Only notify on failures or release tag pushes — green branch/PR builds stay silent
          if [ "$OVERALL_TEXT" = "Build Passed" ] && [[ "$GITHUB_REF" != refs/tags/* ]]; then
            echo "### ⏭️ Build passed — skipping Slack (only failures and releases notify)" >> $GITHUB_STEP_SUMMARY
            exit 0
          fi

          # Branch or tag name
          REF="${GITHUB_REF#refs/heads/}"
          REF="${REF#refs/tags/}"
          REF="${REF#refs/pull/}"

          # Sanitize commit message: first line only, truncate, sourced from env (not inline interpolation)
          COMMIT_MSG=$(printf '%s' "$COMMIT_MSG_RAW" | head -1 | cut -c1-80)

          # Build JSON payload safely with jq to prevent any injection via user-controlled values
          PAYLOAD=$(jq -n \
            --arg header "${OVERALL_ICON}  EDDI CI: ${OVERALL_TEXT}" \
            --arg branch "$REF" \
            --arg actor "$GH_ACTOR" \
            --arg commit "$COMMIT_MSG" \
            --arg build_icon "$(status_icon $BUILD)" \
            --arg it_icon "$(status_icon $IT)" \
            --arg docker_icon "$(status_icon $DOCKER)" \
            --arg smoke_icon "$(status_icon $SMOKE)" \
            --arg gitleaks_icon "$(status_icon $GITLEAKS)" \
            --arg trivy_icon "$(status_icon $TRIVY)" \
            --arg preflight_icon "$(status_icon $PREFLIGHT)" \
            --arg run_url "$RUN_URL" \
            --arg run_number "$RUN_NUMBER" \
            '{
              blocks: [
                {
                  type: "header",
                  text: { type: "plain_text", text: $header, emoji: true }
                },
                {
                  type: "section",
                  text: {
                    type: "mrkdwn",
                    text: ("*Branch:* `" + $branch + "`  ·  *By:* " + $actor + "\n" + $commit)
                  }
                },
                {
                  type: "section",
                  fields: [
                    { type: "mrkdwn", text: ($build_icon + " Build & Test") },
                    { type: "mrkdwn", text: ($it_icon + " Integration Tests") },
                    { type: "mrkdwn", text: ($docker_icon + " Docker Push") },
                    { type: "mrkdwn", text: ($smoke_icon + " Smoke Test") },
                    { type: "mrkdwn", text: ($gitleaks_icon + " Secret Scan") },
                    { type: "mrkdwn", text: ($trivy_icon + " Vuln Scan") },
                    { type: "mrkdwn", text: ($preflight_icon + " Red Hat Preflight") }
                  ]
                },
                {
                  type: "context",
                  elements: [
                    { type: "mrkdwn", text: ("<" + $run_url + "|View Run #" + $run_number + ">") }
                  ]
                }
              ]
            }')

          curl -sf -X POST "$SLACK_WEBHOOK" \
            -H 'Content-type: application/json' \
            -d "$PAYLOAD"
