pipeline {
  agent {
    kubernetes {
      yaml """
apiVersion: v1
kind: Pod
spec:
  restartPolicy: Never
  serviceAccountName: jenkins
  nodeSelector:
    kubernetes.io/arch: arm64
    node-role.kubernetes.io/worker: "true"
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: hardware
                operator: In
                values:
                  - rpi5
                  - rpi4
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 80
          preference:
            matchExpressions:
              - key: hardware
                operator: In
                values:
                  - rpi5
        - weight: 100
          preference:
            matchExpressions:
              - key: kubernetes.io/hostname
                operator: NotIn
                values:
                  - titan-05
                  - titan-13
                  - titan-15
                  - titan-17
                  - titan-19
  topologySpreadConstraints:
    - maxSkew: 1
      topologyKey: kubernetes.io/hostname
      whenUnsatisfiable: ScheduleAnyway
      labelSelector:
        matchLabels:
          jenkins/jenkins-jenkins-agent: "true"
  containers:
    - name: git
      image: alpine/git:2.47.1
      command:
        - cat
      tty: true
    - name: quality-tools
      image: registry.bstein.dev/bstein/quality-tools:sonar8.0.1-trivy0.70.0-db20260422-arm64
      command:
        - cat
      tty: true
    - name: semgrep
      image: semgrep/semgrep:1.171.0
      command:
        - cat
      tty: true
    - name: kaniko
      image: gcr.io/kaniko-project/executor:v1.23.2-debug
      command:
        - /busybox/cat
      tty: true
      resources:
        requests:
          cpu: "100m"
          memory: "1Gi"
        limits:
          cpu: "1500m"
          memory: "2Gi"
"""
    }
  }
  environment {
    SUITE_NAME = 'data_prepper'
    PUSHGATEWAY_URL = 'http://platform-quality-gateway.monitoring.svc.cluster.local:9091'
    SONARQUBE_HOST_URL = 'http://sonarqube.quality.svc.cluster.local:9000'
    SONARQUBE_PROJECT_KEY = 'data_prepper'
    SONARQUBE_TOKEN = credentials('sonarqube-token')
    QUALITY_GATE_SONARQUBE_ENFORCE = '1'
    QUALITY_GATE_SONARQUBE_REPORT = 'build/sonarqube-quality-gate.json'
    QUALITY_GATE_SEMGREP_ENFORCE = '0'
    QUALITY_GATE_SEMGREP_REPORT = 'build/semgrep-report.json'
    QUALITY_GATE_IRONBANK_ENFORCE = '1'
    QUALITY_GATE_IRONBANK_REQUIRED = '1'
    QUALITY_GATE_IRONBANK_REPORT = 'build/ironbank-compliance.json'
  }
  parameters {
    string(name: 'HARBOR_REPO', defaultValue: 'registry.bstein.dev/streaming/data-prepper', description: 'Docker repository for Data Prepper')
    string(name: 'IMAGE_TAG', defaultValue: '2.8.0', description: 'Image tag to publish')
    booleanParam(name: 'PUSH_LATEST', defaultValue: true, description: 'Also push the latest tag')
  }
  options {
    disableConcurrentBuilds()
    buildDiscarder(logRotator(daysToKeepStr: '30', numToKeepStr: '200', artifactDaysToKeepStr: '30', artifactNumToKeepStr: '120'))
  }
  stages {
    stage('Checkout') {
      steps {
        container('git') {
          checkout scm
        }
      }
    }
    stage('Collect quality evidence') {
      steps {
        container('semgrep') {
          sh '''#!/bin/sh
            set -eu
            mkdir -p build
            set +e
            semgrep scan --config auto --metrics=off --json --output build/semgrep.json \
              services/logging/Jenkinsfile.data-prepper \
              dockerfiles/Dockerfile.data-prepper
            semgrep_rc=$?
            set -e
            printf '%s\n' "${semgrep_rc}" > build/semgrep.rc
            python3 ci/scripts/semgrep_report.py --semgrep-json build/semgrep.json --exit-code "${semgrep_rc}" --output "${QUALITY_GATE_SEMGREP_REPORT}" --sonar-issues-output build/semgrep-sonar-issues.json
          '''
        }
        container('quality-tools') {
          sh '''#!/usr/bin/env bash
            set -euo pipefail
            mkdir -p build
            args=(
              "-Dsonar.host.url=${SONARQUBE_HOST_URL}"
              "-Dsonar.login=${SONARQUBE_TOKEN}"
              "-Dsonar.projectKey=${SONARQUBE_PROJECT_KEY}"
              "-Dsonar.projectName=${SONARQUBE_PROJECT_KEY}"
              "-Dsonar.sources=services/logging,dockerfiles"
              "-Dsonar.inclusions=services/logging/Jenkinsfile.data-prepper,dockerfiles/Dockerfile.data-prepper"
              "-Dsonar.exclusions=**/.git/**,**/build/**,**/dist/**,**/node_modules/**,**/.venv/**,**/__pycache__/**"
            )
            [ -f build/semgrep-sonar-issues.json ] && args+=("-Dsonar.externalIssuesReportPaths=build/semgrep-sonar-issues.json")
            set +e
            sonar-scanner "${args[@]}" | tee build/sonar-scanner.log
            sonar_rc=${PIPESTATUS[0]}
            sonar_report="${QUALITY_GATE_SONARQUBE_REPORT:-build/sonarqube-quality-gate.json}"
            host="${SONARQUBE_HOST_URL%/}"
            query="$(printf '%s' "${SONARQUBE_PROJECT_KEY}" | sed 's/ /%20/g')"
            if [ -s .scannerwork/report-task.txt ]; then
              task_id="$(awk -F= '$1 == "ceTaskId" {print $2}' .scannerwork/report-task.txt | head -1)"
              if [ -n "${task_id}" ]; then
                deadline=$((SECONDS + 180))
                while [ "${SECONDS}" -lt "${deadline}" ]; do
                  if [ -n "${SONARQUBE_TOKEN:-}" ]; then
                    auth="$(printf '%s:' "${SONARQUBE_TOKEN}" | base64 | tr -d '\\n')"
                    ce_payload="$(curl -fsS -H "Authorization: Basic ${auth}" "${host}/api/ce/task?id=${task_id}" 2>/dev/null || true)"
                  else
                    ce_payload="$(curl -fsS "${host}/api/ce/task?id=${task_id}" 2>/dev/null || true)"
                  fi
                  ce_status="$(printf '%s' "${ce_payload}" | jq -r '.task.status // empty' 2>/dev/null || true)"
                  case "${ce_status}" in
                    SUCCESS|FAILED|CANCELED) break ;;
                  esac
                  sleep 3
                done
              fi
            fi
            sonar_ok=0
            if [ -n "${SONARQUBE_TOKEN:-}" ]; then
              auth="$(printf '%s:' "${SONARQUBE_TOKEN}" | base64 | tr -d '\\n')"
              if command -v curl >/dev/null 2>&1; then
                curl -fsS -H "Authorization: Basic ${auth}" "${host}/api/qualitygates/project_status?projectKey=${query}" > "${sonar_report}" && sonar_ok=1
              elif command -v wget >/dev/null 2>&1; then
                wget -qO "${sonar_report}" --header="Authorization: Basic ${auth}" "${host}/api/qualitygates/project_status?projectKey=${query}" && sonar_ok=1
              fi
            elif command -v curl >/dev/null 2>&1; then
              curl -fsS "${host}/api/qualitygates/project_status?projectKey=${query}" > "${sonar_report}" && sonar_ok=1
            elif command -v wget >/dev/null 2>&1; then
              wget -qO "${sonar_report}" "${host}/api/qualitygates/project_status?projectKey=${query}" && sonar_ok=1
            fi
            if [ "${sonar_ok}" -ne 1 ]; then
              cat > "${sonar_report}" <<EOF
{
  "status": "ERROR",
  "error": "sonarqube query failed"
}
EOF
            fi
            sonar_status="$(jq -r '.projectStatus.status // .status // .qualityGate.status // empty' "${sonar_report}" 2>/dev/null || true)"
            if [ -z "${sonar_status}" ]; then
              cat > "${sonar_report}" <<EOF
{
  "status": "ERROR",
  "error": "sonarqube response missing projectStatus.status"
}
EOF
            fi
            scan_root=build/data-prepper-supply-chain-scan
            rm -rf "${scan_root}"
            mkdir -p "${scan_root}/dockerfiles" "${scan_root}/services/logging"
            cp dockerfiles/Dockerfile.data-prepper "${scan_root}/dockerfiles/Dockerfile.data-prepper"
            cp services/logging/Jenkinsfile.data-prepper "${scan_root}/services/logging/Jenkinsfile.data-prepper"
            trivy fs --cache-dir "${TRIVY_CACHE_DIR}" --skip-db-update --timeout 5m --no-progress --format json --output build/trivy-fs.json --scanners vuln,secret,misconfig --severity HIGH,CRITICAL "${scan_root}"
            trivy_rc=$?
            set -e
            printf '%s\n' "${sonar_rc}" > build/sonarqube-analysis.rc
            if [ ! -s build/trivy-fs.json ]; then
              cat > build/ironbank-compliance.json <<EOF
{"status":"failed","compliant":false,"scanner":"trivy","scan_type":"filesystem","error":"trivy did not produce JSON output","trivy_rc":${trivy_rc}}
EOF
              exit 0
            fi
            critical="$(jq '[.Results[]? | .Vulnerabilities[]? | select(.Severity=="CRITICAL")] | length' build/trivy-fs.json)"
            high="$(jq '[.Results[]? | .Vulnerabilities[]? | select(.Severity=="HIGH")] | length' build/trivy-fs.json)"
            secrets="$(jq '[.Results[]? | .Secrets[]?] | length' build/trivy-fs.json)"
            misconfigs="$(jq '[.Results[]? | .Misconfigurations[]? | select(.Status=="FAIL" and (.Severity=="CRITICAL" or .Severity=="HIGH"))] | length' build/trivy-fs.json)"
            status=ok
            compliant=true
            if [ "${critical}" -gt 0 ] || [ "${secrets}" -gt 0 ] || [ "${misconfigs}" -gt 0 ]; then
              status=failed
              compliant=false
            fi
            jq -n --arg status "${status}" --argjson compliant "${compliant}" --argjson critical "${critical}" --argjson high "${high}" --argjson secrets "${secrets}" --argjson misconfigs "${misconfigs}" --argjson trivy_rc "${trivy_rc}" \
              '{status:$status, compliant:$compliant, category:"image_compliance", scan_type:"filesystem", scanner:"trivy", critical_vulnerabilities:$critical, high_vulnerabilities:$high, secrets:$secrets, high_or_critical_misconfigurations:$misconfigs, trivy_rc:$trivy_rc, high_vulnerability_policy:"observe"}' > build/ironbank-compliance.json
          '''
        }
        container('git') {
          sh '''
            set -euo pipefail
            apk add --no-cache curl jq >/dev/null 2>&1 || true
            mkdir -p build

            sonar_report="${QUALITY_GATE_SONARQUBE_REPORT:-build/sonarqube-quality-gate.json}"
            if [ ! -f "${sonar_report}" ]; then
              if [ -n "${SONARQUBE_HOST_URL:-}" ] && [ -n "${SONARQUBE_PROJECT_KEY:-}" ]; then
                host="${SONARQUBE_HOST_URL%/}"
                query="$(printf '%s' "${SONARQUBE_PROJECT_KEY}" | sed 's/ /%20/g')"
                sonar_ok=0
                if [ -n "${SONARQUBE_TOKEN:-}" ]; then
                  auth="$(printf '%s:' "${SONARQUBE_TOKEN}" | base64 | tr -d '\\n')"
                  if curl -fsS -H "Authorization: Basic ${auth}" "${host}/api/qualitygates/project_status?projectKey=${query}" > "${sonar_report}"; then
                    sonar_ok=1
                  fi
                else
                  if curl -fsS "${host}/api/qualitygates/project_status?projectKey=${query}" > "${sonar_report}"; then
                    sonar_ok=1
                  fi
                fi
                if [ "${sonar_ok}" -ne 1 ]; then
                  cat > "${sonar_report}" <<EOF
{
  "status": "ERROR",
  "error": "sonarqube query failed"
}
EOF
                fi
              else
                cat > "${sonar_report}" <<EOF
{
  "status": "ERROR",
  "note": "missing SONARQUBE_HOST_URL and/or SONARQUBE_PROJECT_KEY"
}
EOF
              fi
            fi

            ironbank_report="${QUALITY_GATE_IRONBANK_REPORT:-build/ironbank-compliance.json}"
            if [ ! -f "${ironbank_report}" ]; then
              status="${IRONBANK_COMPLIANCE_STATUS:-unknown}"
              compliant="${IRONBANK_COMPLIANT:-}"
              if [ -n "${compliant}" ]; then
                compliant_lc="$(printf '%s' "${compliant}" | tr '[:upper:]' '[:lower:]')"
                compliant_json="null"
                case "${compliant_lc}" in
                  1|true|yes|on) compliant_json="true" ;;
                  0|false|no|off) compliant_json="false" ;;
                esac
                cat > "${ironbank_report}" <<EOF
{
  "status": "${status}",
  "compliant": ${compliant_json},
  "note": "Set IRONBANK_COMPLIANCE_STATUS/IRONBANK_COMPLIANT or write build/ironbank-compliance.json in image-building repos."
}
EOF
              else
                cat > "${ironbank_report}" <<EOF
{
  "status": "${status}",
  "note": "Set IRONBANK_COMPLIANCE_STATUS/IRONBANK_COMPLIANT or write build/ironbank-compliance.json in image-building repos."
}
EOF
              fi
            fi
          '''
        }
      }
    }
    stage('Validation tests') {
      steps {
        container('git') {
          sh '''#!/usr/bin/env sh
            set -eu
            mkdir -p build
            failures=0
            cases=""
            dockerfile_present_status="skipped"
            pipeline_config_present_status="skipped"
            logging_kustomization_includes_data_prepper_status="skipped"

            add_case() {
              name="$1"
              message="$2"
              status="passed"
              if [ -n "${message}" ]; then
                status="failed"
                failures=$((failures + 1))
                cases="${cases}"'<testcase classname="data_prepper.packaging" name="'"${name}"'"><failure message="'"${message}"'" /></testcase>'
              else
                cases="${cases}"'<testcase classname="data_prepper.packaging" name="'"${name}"'" />'
              fi
              case "${name}" in
                dockerfile_present) dockerfile_present_status="${status}" ;;
                pipeline_config_present) pipeline_config_present_status="${status}" ;;
                logging_kustomization_includes_data_prepper) logging_kustomization_includes_data_prepper_status="${status}" ;;
              esac
            }

            if [ -s dockerfiles/Dockerfile.data-prepper ]; then
              add_case "dockerfile_present" ""
            else
              add_case "dockerfile_present" "dockerfiles/Dockerfile.data-prepper is missing or empty"
            fi

            if [ -s services/logging/scripts/data_prepper_pipelines.yaml ]; then
              add_case "pipeline_config_present" ""
            else
              add_case "pipeline_config_present" "data_prepper_pipelines.yaml is missing or empty"
            fi

            kustomization_contents="$(cat services/logging/kustomization.yaml 2>/dev/null || true)"
            case "${kustomization_contents}" in
              *data-prepper-helmrelease.yaml*) add_case "logging_kustomization_includes_data_prepper" "" ;;
              *) add_case "logging_kustomization_includes_data_prepper" "services/logging/kustomization.yaml does not include data-prepper HelmRelease" ;;
            esac

            cat > build/junit-data-prepper.xml <<EOF
<testsuite name="data_prepper.packaging" tests="3" failures="${failures}" errors="0" skipped="0">
${cases}
</testsuite>
EOF
            passed=$((3 - failures))
            cat > build/test-counts.env <<EOF
test_passed_count=${passed}
test_failed_count=${failures}
test_error_count=0
test_skipped_count=0
EOF
            cat > build/testcase-status.env <<EOF
dockerfile_present_status=${dockerfile_present_status}
pipeline_config_present_status=${pipeline_config_present_status}
logging_kustomization_includes_data_prepper_status=${logging_kustomization_includes_data_prepper_status}
EOF
            if [ "${failures}" -ne 0 ]; then
              exit 1
            fi
          '''
        }
      }
    }
    stage('Enforce quality gate') {
      steps {
        container('git') {
          sh '''
            set -euo pipefail
            apk add --no-cache jq >/dev/null 2>&1 || true
            fail=0
            enabled() {
              case "$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')" in
                1|true|yes|on) return 0 ;;
                *) return 1 ;;
              esac
            }

            if enabled "${QUALITY_GATE_SONARQUBE_ENFORCE:-1}"; then
              sonar_status="$( (jq -r '.projectStatus.status // .status // .qualityGate.status // empty' build/sonarqube-quality-gate.json 2>/dev/null || true) | tr '[:upper:]' '[:lower:]')"
              [ -n "${sonar_status}" ] || sonar_status="missing"
              case "${sonar_status}" in
                ok|pass|passed|success) ;;
                *)
                  echo "sonarqube gate failed: ${sonar_status}" >&2
                  fail=1
                  ;;
              esac
            fi

            if enabled "${QUALITY_GATE_SEMGREP_ENFORCE:-0}"; then
              semgrep_status="$(jq -r '.status // empty' build/semgrep-report.json 2>/dev/null | tr '[:upper:]' '[:lower:]')"
              [ -n "${semgrep_status}" ] || semgrep_status="missing"
              case "${semgrep_status}" in
                ok|pass|passed|success) ;;
                *)
                  echo "semgrep gate failed: ${semgrep_status}" >&2
                  fail=1
                  ;;
              esac
            fi

            if enabled "${QUALITY_GATE_IRONBANK_ENFORCE:-1}"; then
              ironbank_required="${QUALITY_GATE_IRONBANK_REQUIRED:-1}"
              compliant="$(jq -r '.compliant // empty' build/ironbank-compliance.json 2>/dev/null || true)"
              supply_status=""
              if [ "${compliant}" = "true" ]; then
                supply_status="ok"
              elif [ "${compliant}" = "false" ]; then
                supply_status="failed"
              else
                supply_status="$(jq -r '.status // .result // .compliance // empty' build/ironbank-compliance.json 2>/dev/null | tr '[:upper:]' '[:lower:]')"
              fi
              [ -n "${supply_status}" ] || supply_status="missing"
              case "${supply_status}" in
                ok|pass|passed|success|compliant) ;;
                not_applicable|na|n/a)
                  if enabled "${ironbank_required}"; then
                    echo "supply chain gate required but status=${supply_status}" >&2
                    fail=1
                  fi
                  ;;
                *)
                  if enabled "${ironbank_required}"; then
                    echo "supply chain gate failed: ${supply_status}" >&2
                    fail=1
                  else
                    echo "supply chain gate not passing (${supply_status}) but not required for this run" >&2
                  fi
                  ;;
              esac
            fi

            exit "${fail}"
          '''
        }
      }
    }
    stage('Build & Push') {
      steps {
        container('kaniko') {
          withCredentials([usernamePassword(credentialsId: 'harbor-robot-streaming', usernameVariable: 'HARBOR_USERNAME', passwordVariable: 'HARBOR_PASSWORD')]) {
            sh '''
              set -euo pipefail
              IMAGE_TAG="${IMAGE_TAG:-2.8.0}"
              PUSH_LATEST="${PUSH_LATEST:-true}"
              if [ -z "${HARBOR_REPO:-}" ] || [ "${HARBOR_REPO}" = "registry.bstein.dev/monitoring/data-prepper" ]; then
                HARBOR_REPO="registry.bstein.dev/streaming/data-prepper"
              fi
              mkdir -p /kaniko/.docker
              ref_host="$(echo "${HARBOR_REPO}" | cut -d/ -f1)"
              auth="$(printf "%s:%s" "${HARBOR_USERNAME}" "${HARBOR_PASSWORD}" | base64 | tr -d '\\n')"
              cat > /kaniko/.docker/config.json <<EOF
              {
                "auths": {
                  "${ref_host}": {
                    "auth": "${auth}"
                  }
                }
              }
EOF
              dest_args="--destination ${HARBOR_REPO}:${IMAGE_TAG}"
              if [ "${PUSH_LATEST}" = "true" ]; then
                dest_args="${dest_args} --destination ${HARBOR_REPO}:latest"
              fi
              /kaniko/executor \
                --context "${WORKSPACE}" \
                --dockerfile "${WORKSPACE}/dockerfiles/Dockerfile.data-prepper" \
                --verbosity info \
                ${dest_args}
            '''
          }
        }
      }
    }
  }
  post {
    always {
      script {
        env.QUALITY_OUTCOME = currentBuild.currentResult == 'SUCCESS' ? 'ok' : 'failed'
      }
      script {
        try {
          container('git') {
            sh '''
          set -euo pipefail
          apk add --no-cache curl jq >/dev/null 2>&1 || true
          suite="${SUITE_NAME}"
          gateway="${PUSHGATEWAY_URL}"
          status="${QUALITY_OUTCOME:-failed}"
          fetch_counter() {
            status_name="$1"
            line="$(curl -fsS "${gateway}/metrics" 2>/dev/null | awk -v suite="${suite}" -v status="${status_name}" '
              /platform_quality_gate_runs_total/ {
                if (index($0, "job=\\"platform-quality-ci\\"") && index($0, "suite=\\"" suite "\\"") && index($0, "status=\\"" status "\\"")) {
                  print $2
                  exit
                }
              }
            ' || true)"
            [ -n "${line}" ] && printf '%s\n' "${line}" || printf '0\n'
          }
          ok_count="$(fetch_counter ok)"
          failed_count="$(fetch_counter failed)"
          if [ "${status}" = "ok" ]; then
            ok_count=$((ok_count + 1))
          else
            failed_count=$((failed_count + 1))
          fi
          sonarqube_check="not_applicable"
          if [ -f build/sonarqube-quality-gate.json ]; then
            sonar_status="$( (jq -r '.projectStatus.status // .status // .qualityGate.status // empty' build/sonarqube-quality-gate.json 2>/dev/null || true) | tr '[:upper:]' '[:lower:]')"
            if [ -n "${sonar_status}" ]; then
              case "${sonar_status}" in
                ok|pass|passed|success) sonarqube_check="ok" ;;
                *) sonarqube_check="failed" ;;
              esac
            else
              sonarqube_check="failed"
            fi
          fi
          semgrep_check="not_applicable"
          if [ -f build/semgrep-report.json ]; then
            semgrep_status="$(jq -r '.status // empty' build/semgrep-report.json 2>/dev/null | tr '[:upper:]' '[:lower:]')"
            case "${semgrep_status}" in
              ok|pass|passed|success) semgrep_check="ok" ;;
              "") semgrep_check="failed" ;;
              *) semgrep_check="failed" ;;
            esac
          fi
          supply_chain_check="not_applicable"
          if [ -f build/ironbank-compliance.json ]; then
            compliant="$(jq -r '.compliant // empty' build/ironbank-compliance.json 2>/dev/null)"
            if [ "${compliant}" = "true" ]; then
              supply_chain_check="ok"
            elif [ "${compliant}" = "false" ]; then
              supply_chain_check="failed"
            else
              ironbank_status="$(jq -r '.status // .result // .compliance // empty' build/ironbank-compliance.json 2>/dev/null | tr '[:upper:]' '[:lower:]')"
              case "${ironbank_status}" in
                ok|pass|passed|success|compliant) supply_chain_check="ok" ;;
                "") supply_chain_check="failed" ;;
                *) supply_chain_check="failed" ;;
              esac
            fi
          fi
          gate_glue_check="ok"
          if [ "${status}" != "ok" ]; then
            gate_glue_check="failed"
          fi
          metric_branch_raw="${BRANCH_NAME:-${GIT_BRANCH:-unknown}}"
          metric_branch_raw="${metric_branch_raw#origin/}"
          metric_branch="$(printf '%s' "${metric_branch_raw}" | jq -Rsa . | sed -e 's/^"//' -e 's/"$//')"
          metric_build_number="$(printf '%s' "${BUILD_NUMBER:-unknown}" | jq -Rsa . | sed -e 's/^"//' -e 's/"$//')"
          metric_jenkins_job="$(printf '%s' "${JOB_NAME:-data-prepper}" | jq -Rsa . | sed -e 's/^"//' -e 's/"$//')"
          export METRIC_SUITE="${suite}"
          export METRIC_BRANCH_RAW="${metric_branch_raw}"
          export METRIC_BUILD_NUMBER_RAW="${BUILD_NUMBER:-unknown}"
          export METRIC_JENKINS_JOB_RAW="${JOB_NAME:-data-prepper}"
          if [ ! -s build/test-counts.env ] || [ ! -s build/testcase-status.env ]; then
            cat > build/test-counts.env <<EOF
test_passed_count=0
test_failed_count=0
test_error_count=0
test_skipped_count=1
EOF
            cat > build/testcase-status.env <<EOF
dockerfile_present_status=skipped
pipeline_config_present_status=skipped
logging_kustomization_includes_data_prepper_status=skipped
EOF
          fi
          . build/testcase-status.env
          if [ "${dockerfile_present_status}" = "skipped" ] && [ "${pipeline_config_present_status}" = "skipped" ] && [ "${logging_kustomization_includes_data_prepper_status}" = "skipped" ]; then
            cat > build/testcase-metrics.prom <<METRICS
platform_quality_gate_test_case_result{suite="${suite}",branch="${metric_branch}",build_number="${metric_build_number}",jenkins_job="${metric_jenkins_job}",test="__no_test_cases__",status="skipped"} 1
METRICS
          else
            cat > build/testcase-metrics.prom <<METRICS
platform_quality_gate_test_case_result{suite="${suite}",branch="${metric_branch}",build_number="${metric_build_number}",jenkins_job="${metric_jenkins_job}",test="data_prepper.packaging::dockerfile_present",status="${dockerfile_present_status}"} 1
platform_quality_gate_test_case_result{suite="${suite}",branch="${metric_branch}",build_number="${metric_build_number}",jenkins_job="${metric_jenkins_job}",test="data_prepper.packaging::pipeline_config_present",status="${pipeline_config_present_status}"} 1
platform_quality_gate_test_case_result{suite="${suite}",branch="${metric_branch}",build_number="${metric_build_number}",jenkins_job="${metric_jenkins_job}",test="data_prepper.packaging::logging_kustomization_includes_data_prepper",status="${logging_kustomization_includes_data_prepper_status}"} 1
METRICS
          fi
          . build/test-counts.env
          tests_check="ok"
          if [ "$((test_failed_count + test_error_count))" -gt 0 ]; then
            tests_check="failed"
          fi
          cat > build/platform-quality-metrics.prom <<METRICS
# TYPE platform_quality_gate_runs_total counter
platform_quality_gate_runs_total{suite="${suite}",status="ok"} ${ok_count}
platform_quality_gate_runs_total{suite="${suite}",status="failed"} ${failed_count}
# TYPE data_prepper_quality_gate_tests_total gauge
data_prepper_quality_gate_tests_total{suite="${suite}",result="passed"} ${test_passed_count}
data_prepper_quality_gate_tests_total{suite="${suite}",result="failed"} ${test_failed_count}
data_prepper_quality_gate_tests_total{suite="${suite}",result="error"} ${test_error_count}
data_prepper_quality_gate_tests_total{suite="${suite}",result="skipped"} ${test_skipped_count}
# TYPE platform_quality_gate_workspace_line_coverage_percent gauge
# No coverable project source is present in this packaging suite; report full
# non-applicable coverage so rollups do not confuse N/A with uncovered code.
platform_quality_gate_workspace_line_coverage_percent{suite="${suite}"} 100
# TYPE platform_quality_gate_source_files_total gauge
platform_quality_gate_source_files_total{suite="${suite}"} 0
# TYPE platform_quality_gate_source_lines_over_500_total gauge
platform_quality_gate_source_lines_over_500_total{suite="${suite}"} 0
# TYPE platform_quality_gate_build_info gauge
platform_quality_gate_build_info{suite="${suite}",branch="${metric_branch}",build_number="${metric_build_number}",jenkins_job="${metric_jenkins_job}"} 1
# TYPE data_prepper_quality_gate_checks_total gauge
data_prepper_quality_gate_checks_total{suite="${suite}",check="tests",result="${tests_check}"} 1
data_prepper_quality_gate_checks_total{suite="${suite}",check="coverage",result="not_applicable"} 1
data_prepper_quality_gate_checks_total{suite="${suite}",check="loc",result="not_applicable"} 1
data_prepper_quality_gate_checks_total{suite="${suite}",check="docs_naming",result="not_applicable"} 1
data_prepper_quality_gate_checks_total{suite="${suite}",check="gate_glue",result="${gate_glue_check}"} 1
data_prepper_quality_gate_checks_total{suite="${suite}",check="sonarqube",result="${sonarqube_check}"} 1
data_prepper_quality_gate_checks_total{suite="${suite}",check="semgrep",result="${semgrep_check}"} 1
data_prepper_quality_gate_checks_total{suite="${suite}",check="supply_chain",result="${supply_chain_check}"} 1
# TYPE platform_quality_gate_test_case_result gauge
METRICS
          cat build/testcase-metrics.prom >> build/platform-quality-metrics.prom
          push_status="$(
            curl -sS -o build/pushgateway-response.txt -w '%{http_code}' -X PUT \
              --data-binary @build/platform-quality-metrics.prom \
              "${gateway}/metrics/job/platform-quality-ci/suite/${suite}" || true
          )"
          case "${push_status}" in
            200|202) ;;
            *)
              echo "warning: metrics push failed for suite=${suite} status=${push_status}" >&2
              cat build/pushgateway-response.txt >&2 || true
              ;;
          esac
            '''
          }
          if (fileExists('build/junit-data-prepper.xml')) {
            echo 'JUnit XML generated and archived under build/; Jenkins junit step is not installed on this controller.'
          }
          archiveArtifacts artifacts: 'build/**', allowEmptyArchive: true, fingerprint: true
        } catch (Throwable err) {
          if (err.class.simpleName == 'MissingContextVariableException') {
            echo 'workspace unavailable; skipping post-build metrics and artifact collection'
          } else {
            throw err
          }
        }
      }
    }
  }
}
