name: Prepare Or Publish Packages

on:
  workflow_dispatch:
    inputs:
      mode:
        description: Prepare builds before release, or publish prepared artifacts after release
        required: true
        type: choice
        options:
          - prepare
          - publish
      tag:
        description: Release tag in vX.Y.Z form
        required: true
        type: string
      candidate_sha:
        description: Full 40-character commit SHA signed by the release tag
        required: true
        type: string
      prepare_run_id:
        description: Successful prepare workflow run ID (required for publish)
        required: false
        type: string
      source_archive_sha256:
        description: Reviewed source archive SHA-256 from the prepare run (required for publish)
        required: false
        type: string
      oci_manifest_digest:
        description: Reviewed OCI manifest sha256 digest from the prepare run (required for publish)
        required: false
        type: string

concurrency:
  group: pgcontext-release
  cancel-in-progress: false

permissions:
  contents: read

env:
  IMAGE_NAME: ghcr.io/evokoa/pgcontext

jobs:
  validate:
    name: Validate release
    runs-on: ubuntu-24.04
    outputs:
      mode: ${{ steps.release.outputs.mode }}
      tag: ${{ steps.release.outputs.tag }}
      version: ${{ steps.release.outputs.version }}
      commit_sha: ${{ steps.release.outputs.candidate_sha }}
      short_sha: ${{ steps.release.outputs.short_sha }}
      prepare_run_id: ${{ steps.release.outputs.prepare_run_id }}
      source_archive_sha256: ${{ steps.release.outputs.source_archive_sha256 }}
      oci_manifest_digest: ${{ steps.release.outputs.oci_manifest_digest }}
    steps:
      - name: Resolve release input
        id: release
        env:
          MODE: ${{ inputs.mode }}
          TAG: ${{ inputs.tag }}
          CANDIDATE_SHA: ${{ inputs.candidate_sha }}
          PREPARE_RUN_ID: ${{ inputs.prepare_run_id }}
          SOURCE_ARCHIVE_SHA256: ${{ inputs.source_archive_sha256 }}
          OCI_MANIFEST_DIGEST: ${{ inputs.oci_manifest_digest }}
        run: |
          set -euo pipefail
          if [[ "${MODE}" != prepare && "${MODE}" != publish ]]; then
            echo "Mode must be prepare or publish: ${MODE}" >&2
            exit 1
          fi
          if [[ ! "${TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
            echo "Release tag must use vX.Y.Z form: ${TAG}" >&2
            exit 1
          fi
          if [[ ! "${CANDIDATE_SHA}" =~ ^[0-9a-f]{40}$ ]]; then
            echo "Candidate SHA must be a full lowercase Git commit: ${CANDIDATE_SHA}" >&2
            exit 1
          fi
          if [[ "${{ github.sha }}" != "${CANDIDATE_SHA}" ]]; then
            echo "Dispatch this workflow from the candidate tag/commit ${CANDIDATE_SHA}" >&2
            exit 1
          fi
          if [[ "${MODE}" == publish ]]; then
            if [[ ! "${PREPARE_RUN_ID}" =~ ^[1-9][0-9]*$ ]]; then
              echo "Publish mode requires a prepare workflow run ID" >&2
              exit 1
            fi
            if [[ ! "${SOURCE_ARCHIVE_SHA256}" =~ ^[0-9a-f]{64}$ ]]; then
              echo "Publish mode requires the reviewed source archive SHA-256" >&2
              exit 1
            fi
            if [[ ! "${OCI_MANIFEST_DIGEST}" =~ ^sha256:[0-9a-f]{64}$ ]]; then
              echo "Publish mode requires the reviewed OCI manifest digest" >&2
              exit 1
            fi
          fi
          echo "mode=${MODE}" >>"${GITHUB_OUTPUT}"
          echo "tag=${TAG}" >>"${GITHUB_OUTPUT}"
          echo "version=${TAG#v}" >>"${GITHUB_OUTPUT}"
          echo "candidate_sha=${CANDIDATE_SHA}" >>"${GITHUB_OUTPUT}"
          echo "short_sha=${CANDIDATE_SHA:0:12}" >>"${GITHUB_OUTPUT}"
          echo "prepare_run_id=${PREPARE_RUN_ID}" >>"${GITHUB_OUTPUT}"
          echo "source_archive_sha256=${SOURCE_ARCHIVE_SHA256}" >>"${GITHUB_OUTPUT}"
          echo "oci_manifest_digest=${OCI_MANIFEST_DIGEST}" >>"${GITHUB_OUTPUT}"

      - name: Verify signed annotated release tag
        env:
          GH_TOKEN: ${{ github.token }}
          GH_REPO: ${{ github.repository }}
          TAG: ${{ steps.release.outputs.tag }}
          CANDIDATE_SHA: ${{ steps.release.outputs.candidate_sha }}
        run: |
          set -euo pipefail
          ref="$(gh api "repos/${GH_REPO}/git/ref/tags/${TAG}")"
          if [[ "$(jq -r .object.type <<<"${ref}")" != tag ]]; then
            echo "Release tag must be annotated: ${TAG}" >&2
            exit 1
          fi
          tag_object="$(gh api "repos/${GH_REPO}/git/tags/$(jq -r .object.sha <<<"${ref}")")"
          if [[ "$(jq -r .verification.verified <<<"${tag_object}")" != true ]]; then
            echo "Release tag signature is not verified by GitHub: ${TAG}" >&2
            jq -r '.verification.reason' <<<"${tag_object}" >&2
            exit 1
          fi
          if [[ "$(jq -r .object.type <<<"${tag_object}")" != commit ]] || \
             [[ "$(jq -r .object.sha <<<"${tag_object}")" != "${CANDIDATE_SHA}" ]]; then
            echo "Release tag does not point directly to candidate SHA ${CANDIDATE_SHA}" >&2
            exit 1
          fi

      - name: Checkout release tag
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
        with:
          ref: ${{ steps.release.outputs.tag }}
          fetch-depth: 0
          persist-credentials: false

      - name: Verify checkout matches candidate SHA
        run: test "$(git rev-parse HEAD)" = "${{ steps.release.outputs.candidate_sha }}"

      - name: Validate release metadata
        run: scripts/validate-release.py --tag "${{ steps.release.outputs.tag }}" --check-master

  verify-release-draft:
    name: Verify GitHub Release is an empty draft
    if: needs.validate.outputs.mode == 'publish'
    needs: validate
    runs-on: ubuntu-24.04
    permissions:
      contents: write
    steps:
      - name: Verify empty draft
        env:
          GH_TOKEN: ${{ github.token }}
          GH_REPO: ${{ github.repository }}
          TAG: ${{ needs.validate.outputs.tag }}
        run: |
          set -euo pipefail
          release="$(gh api --paginate "repos/${GH_REPO}/releases" --jq ".[] | select(.tag_name == \"${TAG}\")")"
          [[ -n "${release}" ]] || { echo "Draft release does not exist: ${TAG}" >&2; exit 1; }
          test "$(jq -r .draft <<<"${release}")" = true
          test "$(jq '.assets | length' <<<"${release}")" -eq 0

  pgxn-meta:
    name: Validate PGXN metadata
    needs: validate
    runs-on: ubuntu-24.04
    container: pgxn/pgxn-tools@sha256:a54f1e66c563c7e47b34ed08597d0ee5da1168303b61ef47e66a4a9904ab849d
    steps:
      - name: Checkout release tag
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
        with:
          ref: ${{ needs.validate.outputs.tag }}
          persist-credentials: false

      - name: Validate canonical PGXN metadata
        run: validate_pgxn_meta META.json

  pgxn-artifact:
    name: Build and attest source payload
    if: needs.validate.outputs.mode == 'prepare'
    needs:
      - validate
      - pgxn-meta
    runs-on: ubuntu-24.04
    permissions:
      contents: read
      id-token: write
      attestations: write
      artifact-metadata: write
    steps:
      - name: Checkout release tag
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
        with:
          ref: ${{ needs.validate.outputs.tag }}
          persist-credentials: false

      - name: Build and verify source payload
        run: release/build-packages.sh --out-dir dist "${{ needs.validate.outputs.tag }}"

      - name: Attest source archive provenance
        uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4
        with:
          subject-path: dist/pgContext-${{ needs.validate.outputs.version }}.zip

      - name: Upload source payload
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
        with:
          name: source-payload-${{ needs.validate.outputs.version }}
          path: dist/*
          if-no-files-found: error
          retention-days: 7

  approve-publishing:
    name: Approve package publishing
    if: needs.validate.outputs.mode == 'publish'
    needs:
      - validate
      - pgxn-meta
      - verify-release-draft
      - verify-source-attestation
    runs-on: ubuntu-24.04
    environment: release
    steps:
      - name: Approved
        run: echo "Approved PGXN and Docker package publishing."

  verify-source-attestation:
    name: Verify prepared source attestation
    if: needs.validate.outputs.mode == 'publish'
    needs: validate
    runs-on: ubuntu-24.04
    permissions:
      actions: read
      attestations: read
      contents: read
    steps:
      - name: Download attested source payload
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
        with:
          name: source-payload-${{ needs.validate.outputs.version }}
          path: dist
          github-token: ${{ github.token }}
          run-id: ${{ needs.validate.outputs.prepare_run_id }}

      - name: Verify source checksum and provenance signature
        env:
          GH_TOKEN: ${{ github.token }}
          GH_REPO: ${{ github.repository }}
          EXPECTED_SHA256: ${{ needs.validate.outputs.source_archive_sha256 }}
          VERSION: ${{ needs.validate.outputs.version }}
          CANDIDATE_SHA: ${{ needs.validate.outputs.commit_sha }}
        run: |
          set -euo pipefail
          archive="dist/pgContext-${VERSION}.zip"
          echo "${EXPECTED_SHA256}  ${archive}" | sha256sum --check --strict
          gh attestation verify "${archive}" \
            --repo "${GH_REPO}" \
            --signer-workflow "${GH_REPO}/.github/workflows/release.yml" \
            --source-digest "${CANDIDATE_SHA}"

  publish-pgxn:
    name: Publish PGXN package
    if: needs.validate.outputs.mode == 'publish'
    needs:
      - validate
      - approve-publishing
      - publish-docker-verify
      - docker-verify-default
    runs-on: ubuntu-24.04
    permissions:
      actions: read
      contents: read
    container: pgxn/pgxn-tools@sha256:a54f1e66c563c7e47b34ed08597d0ee5da1168303b61ef47e66a4a9904ab849d
    steps:
      - name: Checkout release tag
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
        with:
          ref: ${{ needs.validate.outputs.tag }}
          persist-credentials: false

      - name: Download attested source payload
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
        with:
          name: source-payload-${{ needs.validate.outputs.version }}
          path: dist
          github-token: ${{ github.token }}
          run-id: ${{ needs.validate.outputs.prepare_run_id }}

      - name: Verify reviewed source payload
        env:
          EXPECTED_SHA256: ${{ needs.validate.outputs.source_archive_sha256 }}
          VERSION: ${{ needs.validate.outputs.version }}
        run: |
          echo "${EXPECTED_SHA256}  dist/pgContext-${VERSION}.zip" | sha256sum --check --strict
          scripts/verify-release-payload.py \
            --tag "${{ needs.validate.outputs.tag }}" \
            --candidate-sha "${{ needs.validate.outputs.commit_sha }}" \
            dist
          validate_pgxn_meta META.json

      - name: Publish to PGXN
        env:
          PGXN_USERNAME: ${{ secrets.PGXN_USERNAME }}
          PGXN_PASSWORD: ${{ secrets.PGXN_PASSWORD }}
        run: pgxn-release "dist/pgContext-${{ needs.validate.outputs.version }}.zip"

  pgxn-verify:
    name: Verify PGXN package
    if: needs.validate.outputs.mode == 'publish'
    needs:
      - validate
      - publish-pgxn
    runs-on: ubuntu-24.04
    steps:
      - name: Verify PGXN metadata is visible
        run: |
          set -euo pipefail
          url="https://api.pgxn.org/dist/pgcontext/${{ needs.validate.outputs.version }}/META.json"
          for _ in {1..30}; do
            if curl --fail --silent --show-error --location "${url}" >/tmp/pgxn-meta.json; then
              break
            fi
            sleep 10
          done
          test -s /tmp/pgxn-meta.json

      - name: Verify PGXN version metadata
        env:
          EXPECTED_VERSION: ${{ needs.validate.outputs.version }}
        run: |
          python3 - <<'PY'
          import json
          import os
          from pathlib import Path

          expected = os.environ["EXPECTED_VERSION"]
          meta = json.loads(Path("/tmp/pgxn-meta.json").read_text(encoding="utf-8"))
          if meta.get("name", "").lower() != "pgcontext":
              raise SystemExit(f"Unexpected PGXN distribution: {meta.get('name')!r}")
          if meta.get("version") != expected:
              raise SystemExit(
                  f"Expected PGXN version {expected}, got {meta.get('version')!r}"
              )
          PY

  attach-pgxn-artifact:
    name: Attach source payload to GitHub Release
    if: needs.validate.outputs.mode == 'publish'
    needs:
      - validate
      - pgxn-verify
    runs-on: ubuntu-24.04
    permissions:
      actions: read
      contents: write
    steps:
      - name: Checkout release tag
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
        with:
          ref: ${{ needs.validate.outputs.tag }}
          persist-credentials: false

      - name: Download attested source payload
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
        with:
          name: source-payload-${{ needs.validate.outputs.version }}
          path: dist
          github-token: ${{ github.token }}
          run-id: ${{ needs.validate.outputs.prepare_run_id }}

      - name: Verify reviewed source payload
        env:
          EXPECTED_SHA256: ${{ needs.validate.outputs.source_archive_sha256 }}
          VERSION: ${{ needs.validate.outputs.version }}
        run: |
          echo "${EXPECTED_SHA256}  dist/pgContext-${VERSION}.zip" | sha256sum --check --strict
          scripts/verify-release-payload.py \
            --tag "${{ needs.validate.outputs.tag }}" \
            --candidate-sha "${{ needs.validate.outputs.commit_sha }}" \
            dist

      - name: Attach reviewed source payload
        env:
          GH_TOKEN: ${{ github.token }}
          GH_REPO: ${{ github.repository }}
          TAG: ${{ needs.validate.outputs.tag }}
          VERSION: ${{ needs.validate.outputs.version }}
        run: |
          set -euo pipefail
          mkdir -p existing
          while IFS= read -r asset; do
            name="$(basename "${asset}")"
            if gh release view "${TAG}" --json assets --jq '.assets[].name' | grep -Fxq "${name}"; then
              gh release download "${TAG}" --pattern "${name}" --dir existing
              cmp "${asset}" "existing/${name}"
            else
              gh release upload "${TAG}" "${asset}"
            fi
          done < <(find dist -maxdepth 1 -type f -print | sort)

  docker:
    name: Prepare Docker image PG17 ${{ matrix.platform.name }}
    if: needs.validate.outputs.mode == 'prepare'
    needs: validate
    runs-on: ${{ matrix.platform.runner }}
    permissions:
      contents: read
      packages: write
    strategy:
      fail-fast: false
      matrix:
        platform:
          - name: linux/amd64
            runner: ubuntu-24.04
            pair: linux-amd64
          - name: linux/arm64
            runner: ubuntu-24.04-arm
            pair: linux-arm64
    steps:
      - name: Checkout release tag
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
        with:
          ref: ${{ needs.validate.outputs.tag }}
          persist-credentials: false

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3

      - name: Login to GHCR
        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ github.token }}

      - name: Extract Docker metadata
        id: meta
        uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
        with:
          images: ${{ env.IMAGE_NAME }}
          labels: |
            org.opencontainers.image.version=${{ needs.validate.outputs.version }}
            org.opencontainers.image.revision=${{ needs.validate.outputs.commit_sha }}
            org.opencontainers.image.postgresql.major=17

      - name: Build and push by digest
        id: build
        uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
        with:
          context: .
          file: release/docker/Dockerfile
          platforms: ${{ matrix.platform.name }}
          build-args: |
            PG_MAJOR=17
            VERSION=${{ needs.validate.outputs.version }}
            REVISION=${{ needs.validate.outputs.commit_sha }}
          outputs: type=image,push-by-digest=true,name-canonical=true,push=true
          tags: ${{ env.IMAGE_NAME }}
          labels: ${{ steps.meta.outputs.labels }}
          provenance: mode=max
          cache-from: type=gha,scope=pgcontext-pg17-${{ matrix.platform.pair }}
          cache-to: type=gha,mode=max,scope=pgcontext-pg17-${{ matrix.platform.pair }}

      - name: Export digest
        env:
          DIGEST: ${{ steps.build.outputs.digest }}
        run: |
          set -euo pipefail
          mkdir -p "${RUNNER_TEMP}/digests"
          touch "${RUNNER_TEMP}/digests/${DIGEST#sha256:}"

      - name: Upload digest
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
        with:
          name: digests-pg17-${{ matrix.platform.pair }}
          path: ${{ runner.temp }}/digests/*
          if-no-files-found: error
          retention-days: 1

  docker-merge:
    name: Merge prepared Docker image PG17
    if: needs.validate.outputs.mode == 'prepare'
    needs:
      - validate
      - docker
    runs-on: ubuntu-24.04
    permissions:
      contents: read
      packages: write
      id-token: write
      attestations: write
      artifact-metadata: write
    steps:
      - name: Checkout release tag
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
        with:
          ref: ${{ needs.validate.outputs.tag }}
          persist-credentials: false

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3

      - name: Login to GHCR
        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ github.token }}

      - name: Extract Docker metadata
        id: meta
        uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
        with:
          images: ${{ env.IMAGE_NAME }}
          tags: |
            type=raw,value=pg17-sha-${{ needs.validate.outputs.short_sha }}
            type=raw,value=pg17-${{ needs.validate.outputs.tag }}-prepared
            type=raw,value=pg17-${{ needs.validate.outputs.version }}-prepared

      - name: Download platform digests
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
        with:
          path: ${{ runner.temp }}/digests
          pattern: digests-pg17-*
          merge-multiple: true

      - name: Create manifest list and push
        working-directory: ${{ runner.temp }}/digests
        env:
          DOCKER_METADATA_OUTPUT_JSON: ${{ steps.meta.outputs.json }}
        run: |
          set -euo pipefail
          digest_count="$(find . -maxdepth 1 -type f | wc -l | tr -d ' ')"
          if [[ "${digest_count}" != 2 ]]; then
            echo "Expected 2 platform digests for PG17, found ${digest_count}" >&2
            find . -maxdepth 1 -type f -print >&2
            exit 1
          fi
          mapfile -t tags < <(jq -r '.tags[]' <<<"${DOCKER_METADATA_OUTPUT_JSON}")
          tag_args=()
          for tag in "${tags[@]}"; do
            tag_args+=(--tag "${tag}")
          done
          sources=()
          while IFS= read -r digest_file; do
            sources+=("${{ env.IMAGE_NAME }}@sha256:${digest_file#./}")
          done < <(find . -maxdepth 1 -type f -print | sort)
          docker buildx imagetools create "${tag_args[@]}" "${sources[@]}"

      - name: Resolve merged manifest digest
        id: manifest
        run: |
          set -euo pipefail
          image="${{ env.IMAGE_NAME }}:pg17-${{ needs.validate.outputs.tag }}-prepared"
          digest="$(scripts/resolve-oci-digest.sh "${image}")"
          echo "digest=${digest}" >>"${GITHUB_OUTPUT}"

      - name: Attest image provenance
        uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4
        with:
          subject-name: ${{ env.IMAGE_NAME }}
          subject-digest: ${{ steps.manifest.outputs.digest }}
          push-to-registry: true

  docker-verify:
    name: Verify prepared image PG17 ${{ matrix.platform.name }}
    if: needs.validate.outputs.mode == 'prepare'
    needs:
      - validate
      - docker-merge
    runs-on: ${{ matrix.platform.runner }}
    permissions:
      attestations: read
      contents: read
      packages: read
    strategy:
      fail-fast: false
      matrix:
        platform:
          - name: linux/amd64
            runner: ubuntu-24.04
          - name: linux/arm64
            runner: ubuntu-24.04-arm
    steps:
      - name: Checkout release tag
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
        with:
          ref: ${{ needs.validate.outputs.tag }}
          persist-credentials: false

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3

      - name: Login to GHCR
        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ github.token }}

      - name: Pull and verify prepared image
        env:
          GH_TOKEN: ${{ github.token }}
          GH_REPO: ${{ github.repository }}
          PLATFORM: ${{ matrix.platform.name }}
          PREPARED_IMAGE: ${{ env.IMAGE_NAME }}:pg17-${{ needs.validate.outputs.tag }}-prepared
        run: |
          set -euo pipefail
          digest="$(scripts/resolve-oci-digest.sh "${PREPARED_IMAGE}")"
          scripts/verify-release-image.sh --registry "${{ env.IMAGE_NAME }}@${digest}" "${PLATFORM}"
          gh attestation verify "oci://${{ env.IMAGE_NAME }}@${digest}" \
            --bundle-from-oci \
            --repo "${GH_REPO}" \
            --signer-workflow "${GH_REPO}/.github/workflows/release.yml" \
            --source-digest "${{ needs.validate.outputs.commit_sha }}"

  publish-docker:
    name: Publish Docker image PG17
    if: needs.validate.outputs.mode == 'publish'
    needs:
      - validate
      - approve-publishing
    runs-on: ubuntu-24.04
    permissions:
      contents: read
      packages: write
    steps:
      - name: Checkout release tag
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
        with:
          ref: ${{ needs.validate.outputs.tag }}
          persist-credentials: false

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3

      - name: Login to GHCR
        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ github.token }}

      - name: Promote prepared image tags
        env:
          TAG: ${{ needs.validate.outputs.tag }}
          COMMIT_SHA: ${{ needs.validate.outputs.commit_sha }}
          EXPECTED_DIGEST: ${{ needs.validate.outputs.oci_manifest_digest }}
        run: |
          set -euo pipefail
          scripts/promote-release-image.sh \
            "${{ env.IMAGE_NAME }}" "${TAG}" "${COMMIT_SHA}" "${EXPECTED_DIGEST}"

  publish-docker-verify:
    name: Verify published image PG17 ${{ matrix.platform.name }}
    if: needs.validate.outputs.mode == 'publish'
    needs:
      - validate
      - publish-docker
    runs-on: ${{ matrix.platform.runner }}
    permissions:
      contents: read
      packages: read
    strategy:
      fail-fast: false
      matrix:
        platform:
          - name: linux/amd64
            runner: ubuntu-24.04
          - name: linux/arm64
            runner: ubuntu-24.04-arm
    steps:
      - name: Checkout release tag
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
        with:
          ref: ${{ needs.validate.outputs.tag }}
          persist-credentials: false

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3

      - name: Login to GHCR
        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ github.token }}

      - name: Pull and verify published image
        env:
          EXPECTED_DIGEST: ${{ needs.validate.outputs.oci_manifest_digest }}
          PLATFORM: ${{ matrix.platform.name }}
          PUBLISHED_IMAGE: ${{ env.IMAGE_NAME }}:pg17-${{ needs.validate.outputs.tag }}
        run: |
          set -euo pipefail
          digest="$(scripts/resolve-oci-digest.sh "${PUBLISHED_IMAGE}")"
          test "${digest}" = "${EXPECTED_DIGEST}"
          scripts/verify-release-image.sh --registry "${{ env.IMAGE_NAME }}@${EXPECTED_DIGEST}" "${PLATFORM}"

  docker-verify-default:
    name: Verify default published image ${{ matrix.platform.name }}
    if: needs.validate.outputs.mode == 'publish'
    needs:
      - validate
      - publish-docker
    runs-on: ${{ matrix.platform.runner }}
    permissions:
      contents: read
      packages: read
    strategy:
      fail-fast: false
      matrix:
        platform:
          - name: linux/amd64
            runner: ubuntu-24.04
          - name: linux/arm64
            runner: ubuntu-24.04-arm
    steps:
      - name: Checkout release tag
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
        with:
          ref: ${{ needs.validate.outputs.tag }}
          persist-credentials: false

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3

      - name: Login to GHCR
        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ github.token }}

      - name: Pull and verify default image
        env:
          PLATFORM: ${{ matrix.platform.name }}
          DEFAULT_IMAGE: ${{ env.IMAGE_NAME }}:${{ needs.validate.outputs.tag }}
          EXPECTED_DIGEST: ${{ needs.validate.outputs.oci_manifest_digest }}
        run: |
          set -euo pipefail
          digest="$(scripts/resolve-oci-digest.sh "${DEFAULT_IMAGE}")"
          test "${digest}" = "${EXPECTED_DIGEST}"
          scripts/verify-release-image.sh --registry "${{ env.IMAGE_NAME }}@${EXPECTED_DIGEST}" "${PLATFORM}"

  publish-summary:
    name: Publish summary
    if: needs.validate.outputs.mode == 'publish'
    needs:
      - validate
      - pgxn-verify
      - attach-pgxn-artifact
      - publish-docker-verify
      - docker-verify-default
    runs-on: ubuntu-24.04
    steps:
      - name: Summarize published packages
        run: |
          {
            echo "## Published packages"
            echo
            echo "- PGXN: pgContext ${{ needs.validate.outputs.version }}"
            echo "- Docker: ${{ env.IMAGE_NAME }}:pg17-${{ needs.validate.outputs.tag }}"
            echo "- Docker: ${{ env.IMAGE_NAME }}:${{ needs.validate.outputs.tag }}"
          } >>"${GITHUB_STEP_SUMMARY}"

  prepare-summary:
    name: Prepare summary
    if: needs.validate.outputs.mode == 'prepare'
    needs:
      - validate
      - pgxn-artifact
      - docker-verify
    runs-on: ubuntu-24.04
    steps:
      - name: Summarize prepared packages
        run: |
          {
            echo "## Prepared packages"
            echo
            echo "- Commit: ${{ needs.validate.outputs.short_sha }}"
            echo "- Source workflow artifact: source-payload-${{ needs.validate.outputs.version }}"
            echo "- Docker: ${{ env.IMAGE_NAME }}:pg17-${{ needs.validate.outputs.tag }}-prepared"
          } >>"${GITHUB_STEP_SUMMARY}"
