name: Nightly Build & Package
on:
  schedule:
    # Daily at 09:00 UTC (4am EST / 5am EDT)
    - cron: '0 9 * * *'
  workflow_dispatch:
   inputs:
    reason:
      description: "Why are you running this manually?"
      required: true
      default: "manual trigger"
    skip_tests:
      description: "Skip test-nightly-image job?"
      required: false
      default: "false"

env:
  GO_VERSION: '1.26.2'
  NODE_VERSION: '24.12.0'
  GOTOOLCHAIN: auto
  GHCR_REGISTRY: ghcr.io
  DOCKERHUB_REGISTRY: docker.io
  IMAGE_NAME: wikid82/charon

permissions:
  contents: read

jobs:
  sync-development-to-nightly:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    outputs:
      has_changes: ${{ steps.sync.outputs.has_changes }}

    steps:
      - name: Checkout nightly branch
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2
        with:
          ref: nightly
          fetch-depth: 0
          token: ${{ secrets.CHARON_CI_TRIGGER_TOKEN || secrets.GITHUB_TOKEN }}

      - name: Configure Git
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"

      - name: Sync development to nightly
        id: sync
        env:
          HAS_TRIGGER_TOKEN: ${{ secrets.CHARON_CI_TRIGGER_TOKEN != '' }}
        run: |
          # Fetch both branches to ensure we have the latest remote state
          git fetch origin development
          git fetch origin nightly

          # Sync local nightly with remote nightly to prevent non-fast-forward errors
          echo "Syncing local nightly with remote nightly..."
          git reset --hard origin/nightly

          # Check if there are differences between remote branches
          if git diff --quiet origin/nightly origin/development; then
            echo "No changes to sync from development to nightly"
            echo "has_changes=false" >> "$GITHUB_OUTPUT"
          else
            echo "Syncing changes from development to nightly"
            # Fast-forward merge development into nightly
            git merge origin/development --ff-only -m "chore: sync from development branch [skip ci]" || {
              # If fast-forward fails, force reset to development
              echo "Fast-forward not possible, resetting nightly to development"
              git reset --hard origin/development
            }
            if [[ "$HAS_TRIGGER_TOKEN" != "true" ]]; then
              echo "::warning title=Using GITHUB_TOKEN fallback::Set CHARON_CI_TRIGGER_TOKEN to ensure push-triggered workflows run on nightly."
            fi
            # Force push to handle cases where nightly diverged from development
            git push --force origin nightly
            echo "has_changes=true" >> "$GITHUB_OUTPUT"
          fi

  trigger-nightly-validation:
    name: Trigger Nightly Validation Workflows
    needs: sync-development-to-nightly
    if: needs.sync-development-to-nightly.outputs.has_changes == 'true'
    runs-on: ubuntu-latest
    permissions:
      actions: write
      contents: read
    steps:
      - name: Dispatch Missing Nightly Validation Workflows
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
        with:
          script: |
            const owner = context.repo.owner;
            const repo = context.repo.repo;

            const { data: nightlyBranch } = await github.rest.repos.getBranch({
              owner,
              repo,
              branch: 'nightly',
            });
            const nightlyHeadSha = nightlyBranch.commit.sha;
            core.info(`Current nightly HEAD: ${nightlyHeadSha}`);

            const workflows = [
              { id: 'e2e-tests-split.yml' },
              { id: 'codecov-upload.yml', inputs: { run_backend: 'true', run_frontend: 'true' } },
              { id: 'supply-chain-verify.yml' },
              { id: 'codeql.yml' },
            ];

            core.info('Skipping security-pr.yml: PR-only workflow intentionally excluded from nightly non-PR dispatch');

            for (const workflow of workflows) {
              const { data: workflowRuns } = await github.rest.actions.listWorkflowRuns({
                owner,
                repo,
                workflow_id: workflow.id,
                branch: 'nightly',
                per_page: 50,
              });

              const hasRunForHead = workflowRuns.workflow_runs.some(
                (run) => run.head_sha === nightlyHeadSha,
              );

              if (hasRunForHead) {
                core.info(`Skipping dispatch for ${workflow.id}; run already exists for nightly HEAD`);
                continue;
              }

              await github.rest.actions.createWorkflowDispatch({
                owner,
                repo,
                workflow_id: workflow.id,
                ref: 'nightly',
                ...(workflow.inputs ? { inputs: workflow.inputs } : {}),
              });
              core.info(`Dispatched ${workflow.id} on nightly (missing run for HEAD)`);
            }

  build-and-push-nightly:
    needs: sync-development-to-nightly
    runs-on: ubuntu-latest
    env:
      HAS_DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN != '' }}
    permissions:
      contents: read
      packages: write
      id-token: write
    outputs:
      version: ${{ steps.meta.outputs.version }}
      digest: ${{ steps.resolve_digest.outputs.digest }}

    steps:
      - name: Checkout nightly branch
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2
        with:
          ref: ${{ github.event_name == 'workflow_dispatch' && github.ref || 'nightly' }}
          fetch-depth: 0

      - name: Set lowercase image name
        run: echo "IMAGE_NAME_LC=${IMAGE_NAME,,}" >> "$GITHUB_ENV"

      - name: Set up QEMU
        uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a  # v4.0.0

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd  # v4.0.0

      - name: Resolve Alpine base image digest
        id: alpine
        run: |
          ALPINE_IMAGE_REF=$(grep -m1 'ARG ALPINE_IMAGE=' Dockerfile | cut -d'=' -f2-)
          if [[ -z "$ALPINE_IMAGE_REF" ]]; then
            echo "::error::Failed to parse ALPINE_IMAGE from Dockerfile"
            exit 1
          fi
          echo "Resolved Alpine image: ${ALPINE_IMAGE_REF}"
          echo "image=${ALPINE_IMAGE_REF}" >> "$GITHUB_OUTPUT"

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121  # v4.1.0
        with:
          registry: ${{ env.GHCR_REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Log in to Docker Hub
        if: env.HAS_DOCKERHUB_TOKEN == 'true'
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121  # v4.1.0
        with:
          registry: docker.io
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf  # v6.0.0
        with:
          images: |
            ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
            ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=raw,value=nightly
            type=raw,value=nightly-{{date 'YYYY-MM-DD'}}
            type=sha,prefix=nightly-,format=short
          labels: |
            org.opencontainers.image.title=Charon Nightly
            org.opencontainers.image.description=Nightly build of Charon

      - name: Build and push Docker image
        id: build
        uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f  # v7.1.0
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          build-args: |
            VERSION=nightly-${{ github.sha }}
            VCS_REF=${{ github.sha }}
            BUILD_DATE=${{ github.event.repository.pushed_at }}
            ALPINE_IMAGE=${{ steps.alpine.outputs.image }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          provenance: true
          sbom: true

      - name: Resolve and export image digest
        id: resolve_digest
        run: |
          set -euo pipefail
          DIGEST="${{ steps.build.outputs.digest }}"

          if [[ -z "$DIGEST" ]]; then
            echo "Build action digest empty; querying GHCR registry API..."
            GHCR_TOKEN=$(curl -sf \
              -u "${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}" \
              "https://ghcr.io/token?scope=repository:${{ env.IMAGE_NAME }}:pull&service=ghcr.io" \
              | jq -r '.token')
            DIGEST=$(curl -sfI \
              -H "Authorization: Bearer ${GHCR_TOKEN}" \
              -H "Accept: application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.oci.image.manifest.v1+json" \
              "https://ghcr.io/v2/${{ env.IMAGE_NAME }}/manifests/nightly" \
              | grep -i '^docker-content-digest:' | awk '{print $2}' | tr -d '\r' || true)
            [[ -n "$DIGEST" ]] && echo "Resolved from GHCR API: ${DIGEST}"
          fi

          if [[ -z "$DIGEST" ]]; then
            echo "::error::Could not determine image digest from step output or GHCR registry API"
            exit 1
          fi

          echo "RESOLVED_DIGEST=${DIGEST}" >> "$GITHUB_ENV"
          echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
          echo "Exported digest: ${DIGEST}"

      - name: Record nightly image digest
        run: |
          echo "## 🧾 Nightly Image Digest" >> "$GITHUB_STEP_SUMMARY"
          echo "- ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:nightly@${{ steps.resolve_digest.outputs.digest }}" >> "$GITHUB_STEP_SUMMARY"

      - name: Generate SBOM
        id: sbom_primary
        continue-on-error: true
        uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610  # v0.24.0
        with:
          image: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.resolve_digest.outputs.digest }}
          format: cyclonedx-json
          output-file: sbom-nightly.json
          syft-version: v1.42.1

      - name: Generate SBOM fallback with pinned Syft
        if: always()
        run: |
          set -euo pipefail

          if [[ "${{ steps.sbom_primary.outcome }}" == "success" ]] && [[ -s sbom-nightly.json ]] && jq -e . sbom-nightly.json >/dev/null 2>&1; then
            echo "Primary SBOM generation succeeded with valid JSON; skipping fallback"
            exit 0
          fi

          echo "Primary SBOM generation failed or produced missing/invalid output; using deterministic Syft fallback"

          SYFT_VERSION="v1.42.4"
          OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
          ARCH="$(uname -m)"
          case "$ARCH" in
            x86_64) ARCH="amd64" ;;
            aarch64|arm64) ARCH="arm64" ;;
            *) echo "Unsupported architecture: $ARCH"; exit 1 ;;
          esac

          TARBALL="syft_${SYFT_VERSION#v}_${OS}_${ARCH}.tar.gz"
          BASE_URL="https://github.com/anchore/syft/releases/download/${SYFT_VERSION}"

          curl -fsSLo "$TARBALL" "${BASE_URL}/${TARBALL}"
          curl -fsSLo checksums.txt "${BASE_URL}/syft_${SYFT_VERSION#v}_checksums.txt"

          grep "  ${TARBALL}$" checksums.txt > checksum_line.txt
          sha256sum -c checksum_line.txt

          tar -xzf "$TARBALL" syft
          chmod +x syft

          DIGEST="${{ steps.resolve_digest.outputs.digest }}"
          if [[ -z "$DIGEST" ]]; then
            echo "::error::Digest from resolve_digest step is empty; the digest-resolution step did not complete successfully"
            exit 1
          fi
          ./syft "${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${DIGEST}" -o cyclonedx-json=sbom-nightly.json

      - name: Verify SBOM artifact
        if: always()
        run: |
          set -euo pipefail
          test -s sbom-nightly.json
          jq -e . sbom-nightly.json >/dev/null
          jq -e '
            .bomFormat == "CycloneDX"
            and (.specVersion | type == "string" and length > 0)
            and has("version")
            and has("metadata")
            and (.components | type == "array")
          ' sbom-nightly.json >/dev/null

      - name: Upload SBOM artifact
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a  # v7.0.1
        with:
          name: sbom-nightly
          path: sbom-nightly.json
          retention-days: 30

      # Install Cosign for keyless signing
      - name: Install Cosign
        uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003  # v4.1.1

      # Sign GHCR image with keyless signing (Sigstore/Fulcio)
      - name: Sign GHCR Image
        run: |
          echo "Signing GHCR nightly image with keyless signing..."
          cosign sign --yes "${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.resolve_digest.outputs.digest }}"
          echo "✅ GHCR nightly image signed successfully"

      # Sign Docker Hub image with keyless signing (Sigstore/Fulcio)
      - name: Sign Docker Hub Image
        if: env.HAS_DOCKERHUB_TOKEN == 'true'
        run: |
          echo "Signing Docker Hub nightly image with keyless signing..."
          cosign sign --yes "${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.resolve_digest.outputs.digest }}"
          echo "✅ Docker Hub nightly image signed successfully"

      # Attach SBOM to Docker Hub image
      - name: Attach SBOM to Docker Hub
        if: env.HAS_DOCKERHUB_TOKEN == 'true'
        run: |
          echo "Attaching SBOM to Docker Hub nightly image..."
          cosign attach sbom --sbom sbom-nightly.json "${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.resolve_digest.outputs.digest }}"
          echo "✅ SBOM attached to Docker Hub nightly image"

  test-nightly-image:
    needs: build-and-push-nightly
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: read

    steps:
      - name: Checkout nightly branch
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2
        with:
          ref: ${{ github.event_name == 'workflow_dispatch' && github.ref || 'nightly' }}

      - name: Set lowercase image name
        run: echo "IMAGE_NAME_LC=${IMAGE_NAME,,}" >> "$GITHUB_ENV"

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121  # v4.1.0
        with:
          registry: ${{ env.GHCR_REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Pull nightly image
        run: docker pull "${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:nightly@${{ needs.build-and-push-nightly.outputs.digest }}"

      - name: Run container smoke test
        run: |
          IMAGE_REF="${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:nightly@${{ needs.build-and-push-nightly.outputs.digest }}"
          docker run --name charon-nightly -d \
            -p 8080:8080 \
            "${IMAGE_REF}"

          # Wait for container to become healthy
          echo "⏳ Waiting for Charon to be healthy..."
          MAX_ATTEMPTS=30
          ATTEMPT=0
          while [[ ${ATTEMPT} -lt ${MAX_ATTEMPTS} ]]; do
            ATTEMPT=$((ATTEMPT + 1))
            echo "Attempt ${ATTEMPT}/${MAX_ATTEMPTS}..."
            if docker exec charon-nightly wget -qO- http://127.0.0.1:8080/health > /dev/null 2>&1; then
              echo "✅ Charon is healthy!"
              docker exec charon-nightly wget -qO- http://127.0.0.1:8080/health
              break
            fi
            sleep 2
          done

          if [[ ${ATTEMPT} -ge ${MAX_ATTEMPTS} ]]; then
            echo "❌ Health check failed after ${MAX_ATTEMPTS} attempts"
            docker logs charon-nightly
            docker stop charon-nightly
            docker rm charon-nightly
            exit 1
          fi

          # Cleanup
          docker stop charon-nightly
          docker rm charon-nightly

  # NOTE: Standalone binary builds removed - Charon uses Docker-only deployment
  # The build-nightly-release job that ran GoReleaser for Windows/macOS/Linux binaries
  # was removed because:
  # 1. Charon is distributed exclusively via Docker images
  # 2. Cross-compilation was failing due to Unix-specific syscalls
  # 3. No users download standalone binaries (all use Docker)
  # If standalone binaries are needed in the future, re-add the job with Linux-only targets

  verify-nightly-supply-chain:
    needs: build-and-push-nightly
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: read
      security-events: write

    steps:
      - name: Checkout nightly branch
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2
        with:
          ref: ${{ github.event_name == 'workflow_dispatch' && github.ref || 'nightly' }}

      - name: Set lowercase image name
        run: echo "IMAGE_NAME_LC=${IMAGE_NAME,,}" >> "$GITHUB_ENV"

      - name: Download SBOM
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c  # v8.0.1
        with:
          name: sbom-nightly

      - name: Scan with Grype
        uses: anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2  # v7.4.0
        with:
          sbom: sbom-nightly.json
          fail-build: false
          severity-cutoff: high

      - name: Scan with Trivy
        uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1  # 0.35.0
        with:
          image-ref: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:nightly@${{ needs.build-and-push-nightly.outputs.digest }}
          format: 'sarif'
          output: 'trivy-nightly.sarif'
          version: 'v0.69.3'
          trivyignores: '.trivyignore'

      - name: Upload Trivy results
        uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225  # v4.35.2
        with:
          sarif_file: 'trivy-nightly.sarif'
          category: 'trivy-nightly'

      - name: Security severity policy summary
        run: |
          {
            echo "## 🔐 Nightly Supply Chain Severity Policy"
            echo ""
            echo "- Blocking: Critical, High"
            echo "- Medium: non-blocking by default (report + triage SLA)"
            echo "- Policy file: .github/security-severity-policy.yml"
          } >> "$GITHUB_STEP_SUMMARY"

      - name: Check for Critical/High CVEs
        run: |
          set -euo pipefail

          jq -e . trivy-nightly.sarif >/dev/null

          CRITICAL_COUNT=$(jq -r '
            [
              .runs[] as $run
              | ($run.tool.driver.rules // []) as $rules
              | $run.results[]?
              | . as $result
              | (
                  (
                    if (($result.ruleIndex | type) == "number") then
                      ($rules[$result.ruleIndex].properties["security-severity"] // empty)
                    else
                      empty
                    end
                  )
                  // ([
                      $rules[]?
                      | select((.id // "") == ($result.ruleId // ""))
                      | .properties["security-severity"]
                    ][0] // empty)
                  // empty
                ) as $securitySeverity
              | (try ($securitySeverity | tonumber) catch empty) as $score
              | select($score != null and $score >= 9.0)
            ] | length
          ' trivy-nightly.sarif)

          HIGH_COUNT=$(jq -r '
            [
              .runs[] as $run
              | ($run.tool.driver.rules // []) as $rules
              | $run.results[]?
              | . as $result
              | (
                  (
                    if (($result.ruleIndex | type) == "number") then
                      ($rules[$result.ruleIndex].properties["security-severity"] // empty)
                    else
                      empty
                    end
                  )
                  // ([
                      $rules[]?
                      | select((.id // "") == ($result.ruleId // ""))
                      | .properties["security-severity"]
                    ][0] // empty)
                  // empty
                ) as $securitySeverity
              | (try ($securitySeverity | tonumber) catch empty) as $score
              | select($score != null and $score >= 7.0 and $score < 9.0)
            ] | length
          ' trivy-nightly.sarif)

          MEDIUM_COUNT=$(jq -r '
            [
              .runs[] as $run
              | ($run.tool.driver.rules // []) as $rules
              | $run.results[]?
              | . as $result
              | (
                  (
                    if (($result.ruleIndex | type) == "number") then
                      ($rules[$result.ruleIndex].properties["security-severity"] // empty)
                    else
                      empty
                    end
                  )
                  // ([
                      $rules[]?
                      | select((.id // "") == ($result.ruleId // ""))
                      | .properties["security-severity"]
                    ][0] // empty)
                  // empty
                ) as $securitySeverity
              | (try ($securitySeverity | tonumber) catch empty) as $score
              | select($score != null and $score >= 4.0 and $score < 7.0)
            ] | length
          ' trivy-nightly.sarif)

          {
            echo "- Structured SARIF counts: CRITICAL=${CRITICAL_COUNT}, HIGH=${HIGH_COUNT}, MEDIUM=${MEDIUM_COUNT}"
          } >> "$GITHUB_STEP_SUMMARY"

          # List all Critical/High/Medium findings with details for triage
          # shellcheck disable=SC2016
          LIST_FINDINGS='
            .runs[] as $run
            | ($run.tool.driver.rules // []) as $rules
            | $run.results[]?
            | . as $result
            | (
                (
                  if (($result.ruleIndex | type) == "number") then
                    ($rules[$result.ruleIndex] // {})
                  else
                    {}
                  end
                ) as $ruleByIndex
                | (
                    [$rules[]? | select((.id // "") == ($result.ruleId // ""))][0] // {}
                  ) as $ruleById
                | ($ruleByIndex // $ruleById) as $rule
                | ($rule.properties["security-severity"] // null) as $sev
                | (try ($sev | tonumber) catch null) as $score
                | select($score != null and $score >= 4.0)
                | {
                    id: ($result.ruleId // "unknown"),
                    score: $score,
                    severity: (
                      if $score >= 9.0 then "CRITICAL"
                      elif $score >= 7.0 then "HIGH"
                      else "MEDIUM"
                      end
                    ),
                    message: ($result.message.text // $rule.shortDescription.text // "no description")[0:120]
                  }
              )
          '

          echo ""
          echo "=== Vulnerability Details ==="
          jq -r "[ ${LIST_FINDINGS} ] | sort_by(-.score) | .[] | \"\\(.severity) (\\(.score)): \\(.id) — \\(.message)\"" trivy-nightly.sarif || true
          echo "============================="
          echo ""

          if [ "$CRITICAL_COUNT" -gt 0 ]; then
            echo "❌ Critical vulnerabilities found in nightly build (${CRITICAL_COUNT})"
            {
              echo ""
              echo "### ❌ Critical CVEs blocking nightly"
              echo '```'
              jq -r "[ ${LIST_FINDINGS} | select(.severity == \"CRITICAL\") ] | sort_by(-.score) | .[] | \"\\(.id) (score: \\(.score)): \\(.message)\"" trivy-nightly.sarif || true
              echo '```'
            } >> "$GITHUB_STEP_SUMMARY"
            exit 1
          fi

          if [ "$HIGH_COUNT" -gt 0 ]; then
            echo "❌ High vulnerabilities found in nightly build (${HIGH_COUNT})"
            {
              echo ""
              echo "### ❌ High CVEs blocking nightly"
              echo '```'
              jq -r "[ ${LIST_FINDINGS} | select(.severity == \"HIGH\") ] | sort_by(-.score) | .[] | \"\\(.id) (score: \\(.score)): \\(.message)\"" trivy-nightly.sarif || true
              echo '```'
            } >> "$GITHUB_STEP_SUMMARY"
            exit 1
          fi

          if [ "$MEDIUM_COUNT" -gt 0 ]; then
            echo "::warning::Medium vulnerabilities found in nightly build (${MEDIUM_COUNT}). Non-blocking by policy; triage with SLA per .github/security-severity-policy.yml"
            {
              echo ""
              echo "### ⚠️ Medium CVEs (non-blocking)"
              echo '```'
              jq -r "[ ${LIST_FINDINGS} | select(.severity == \"MEDIUM\") ] | sort_by(-.score) | .[] | \"\\(.id) (score: \\(.score)): \\(.message)\"" trivy-nightly.sarif || true
              echo '```'
            } >> "$GITHUB_STEP_SUMMARY"
          fi

          echo "✅ No Critical/High vulnerabilities found"
