name: Release (Python)

# A manual run with publish=false builds and validates the complete release
# without registry credentials. Publication is accepted only from a manual run
# on the matching python-v* tag when the private publisher-repository identity
# and public-PyPI switch are configured.
on:
  workflow_dispatch:
    inputs:
      publish:
        description: Publish the validated wheels to public PyPI. Must run from a python-v* tag.
        required: true
        type: boolean
        default: false

permissions:
  contents: read

concurrency:
  # Public runs stay globally serialized across tags. Dry runs remain isolated
  # by ref so they do not block an intentional publication.
  group: ${{ github.event_name == 'workflow_dispatch' && inputs.publish && 'python-publication' || format('{0}-{1}', github.workflow, github.ref) }}
  cancel-in-progress: false

jobs:
  build:
    name: Build five wheels
    uses: ./.github/workflows/build-exe-for-python-sdk.yml
    with:
      targets: node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-win-x64
      release: true

  python-compat:
    name: Python ${{ matrix.python }} / installed SDK
    needs: build
    runs-on: ubuntu-latest
    timeout-minutes: 10
    strategy:
      fail-fast: false
      matrix:
        python: ['3.10', '3.14']
    steps:
      - uses: actions/checkout@v6
        with:
          persist-credentials: false

      - uses: actions/setup-python@v6.3.0
        with:
          python-version: ${{ matrix.python }}

      - uses: actions/download-artifact@v8
        with:
          pattern: deepseek_harness_*
          path: dist
          merge-multiple: true

      - name: Resolve installed wheel version
        id: compatibility-version
        run: |
          python - <<'PY' >> "$GITHUB_OUTPUT"
          import runpy

          release = runpy.run_path("scripts/build-python-release.py")
          repository_version = release["repository_version"]()
          print(f"version={release['pep440_version'](repository_version)}")
          PY

      - name: Install local release wheels and run the public entry path
        env:
          VERSION: ${{ steps.compatibility-version.outputs.version }}
        run: |
          python -m pip install \
            "dist/deepseek_harness_sdk-$VERSION-py3-none-any.whl" \
            "dist/deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_x86_64.whl"
          python scripts/smoke-python-runtime.py --scenario sdk-default
          python scripts/smoke-python-runtime.py --scenario sdk-mcp

  validate:
    name: Validate release candidate
    needs: [build, python-compat]
    runs-on: ubuntu-latest
    timeout-minutes: 10
    outputs:
      version: ${{ steps.version.outputs.version }}
    steps:
      - uses: actions/checkout@v6
        with:
          persist-credentials: false

      - uses: actions/setup-python@v6.3.0
        with:
          python-version: '3.10'

      - name: Resolve release version
        id: version
        run: |
          python3 - <<'PY' >> "$GITHUB_OUTPUT"
          import runpy

          release = runpy.run_path("scripts/build-python-release.py")
          repository_version = release["repository_version"]()
          wheel_version = release["pep440_version"](repository_version)
          print(f"repository-version={repository_version}")
          print(f"version={wheel_version}")
          PY

      - name: Authorize publication request
        env:
          PUBLISH: ${{ github.event_name == 'workflow_dispatch' && inputs.publish }}
          PUBLIC_PYPI_RELEASE_ENABLED: ${{ vars.PUBLIC_PYPI_RELEASE_ENABLED }}
          PYPI_PUBLISHER_REPOSITORY: ${{ vars.PYPI_PUBLISHER_REPOSITORY }}
          REPOSITORY: ${{ github.repository }}
          REF_NAME: ${{ github.ref_name }}
          REF_TYPE: ${{ github.ref_type }}
          REPOSITORY_VERSION: ${{ steps.version.outputs.repository-version }}
        run: |
          set -euo pipefail
          if [ "$PUBLISH" = true ]; then
            [ -n "$PYPI_PUBLISHER_REPOSITORY" ] || {
              echo "::error::Set the repository variable PYPI_PUBLISHER_REPOSITORY before publication."
              exit 1
            }
            [ "$REPOSITORY" = "$PYPI_PUBLISHER_REPOSITORY" ] || {
              echo "::error::This repository is not the configured PyPI publisher repository."
              exit 1
            }
            [ "$PUBLIC_PYPI_RELEASE_ENABLED" = true ] || {
              echo "::error::Set PUBLIC_PYPI_RELEASE_ENABLED=true before public publication."
              exit 1
            }
            [ "$REF_TYPE" = tag ] && [ "$REF_NAME" = "python-v$REPOSITORY_VERSION" ] || {
              echo "::error::Publication must run from tag python-v$REPOSITORY_VERSION."
              exit 1
            }
          fi

      - uses: actions/download-artifact@v8
        with:
          pattern: deepseek_harness_*
          path: dist
          merge-multiple: true

      - name: Check release contents
        env:
          VERSION: ${{ steps.version.outputs.version }}
        run: |
          set -euo pipefail
          expected="$(mktemp)"
          actual="$(mktemp)"
          printf '%s\n' \
            "deepseek_harness_runtime_bin-$VERSION-py3-none-macosx_14_0_arm64.whl" \
            "deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_aarch64.whl" \
            "deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_x86_64.whl" \
            "deepseek_harness_runtime_bin-$VERSION-py3-none-win_amd64.whl" \
            "deepseek_harness_sdk-$VERSION-py3-none-any.whl" > "$expected"
          find dist -maxdepth 1 -type f -name '*.whl' -exec basename {} \; | sort > "$actual"
          diff -u "$expected" "$actual"
          while IFS= read -r wheel; do
            size="$(stat -c '%s' "dist/$wheel")"
            [ "$size" -lt 100000000 ] || {
              echo "::error::$wheel is $size bytes; public PyPI accepts at most 100000000 bytes by default."
              exit 1
            }
          done < "$actual"

      - name: Validate package metadata
        run: |
          python -m pip install twine==6.2.0
          python -m twine check dist/*.whl

      - name: Record artifact hashes
        run: |
          cd dist
          sha256sum *.whl | sort -k2 > SHA256SUMS
          cat SHA256SUMS

      - uses: actions/upload-artifact@v7
        with:
          name: python-release-${{ steps.version.outputs.version }}
          path: dist/*
          if-no-files-found: error
          retention-days: 7

  publish-runtime:
    name: Publish runtime wheels to public PyPI
    if: github.event_name == 'workflow_dispatch' && inputs.publish
    needs: validate
    runs-on: ubuntu-latest
    timeout-minutes: 10
    environment: pypi-runtime
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/download-artifact@v8
        with:
          name: python-release-${{ needs.validate.outputs.version }}
          path: dist

      - name: Verify release artifact hashes
        run: cd dist && sha256sum -c SHA256SUMS

      - name: Select runtime wheels
        run: |
          mkdir -p dist/runtime
          mv dist/deepseek_harness_runtime_bin-*.whl dist/runtime/

      - name: Publish runtime wheels
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          packages-dir: dist/runtime/
          # Public attestations reveal the private publisher repository. OIDC
          # authentication remains enabled without uploading that provenance.
          attestations: false

  # Keep the SDK in a dependent job. If its upload fails after the immutable
  # runtime files arrive, "re-run failed jobs" resumes here without attempting
  # to overwrite the runtime release.
  publish-sdk:
    name: Publish SDK wheel to public PyPI
    if: github.event_name == 'workflow_dispatch' && inputs.publish
    needs: [validate, publish-runtime]
    runs-on: ubuntu-latest
    timeout-minutes: 10
    environment: pypi
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/download-artifact@v8
        with:
          name: python-release-${{ needs.validate.outputs.version }}
          path: dist

      - name: Verify release artifact hashes
        run: cd dist && sha256sum -c SHA256SUMS

      - name: Select SDK wheel
        run: |
          mkdir -p dist/sdk
          mv dist/deepseek_harness_sdk-*.whl dist/sdk/

      - name: Publish SDK wheel
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          packages-dir: dist/sdk/
          attestations: false
