name: Release

# Trigger after CI succeeds to avoid releasing before checks finish
on:
  workflow_run:
    workflows: ["CI"]
    types: [completed]
    branches:
      - main

# Prevent fork to access secrets (release workflow should only run on main repo)
concurrency:
  group: releases
  cancel-in-progress: false

env:
  PYTHON_VERSION: "3.14"
  POETRY_VERSION: "2.3.4"

jobs:
  version-bump:
    # Determine next version based on conventional commits since last tag
    if: |
      github.repository == 'kpeacocke/souschef'
      && github.event_name == 'workflow_run'
      && github.event.workflow_run.conclusion == 'success'
      && github.event.workflow_run.head_branch == 'main'
    runs-on: ubuntu-latest
    timeout-minutes: 10
    permissions:
      contents: write  # Need write to update pyproject.toml
      actions: read
    outputs:
      version: ${{ steps.get-version.outputs.version }}
      tag: ${{ steps.get-version.outputs.tag }}

    steps:
    - name: Clone trusted repository snapshot
      run: |
        git clone https://github.com/kpeacocke/souschef.git .
        git checkout main
        git fetch --tags --force

    - name: Set up Python
      uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405  # v6.2.0
      with:
        python-version: ${{ env.PYTHON_VERSION }}

    - name: Install Poetry
      uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a  # v1.4.1
      with:
        version: ${{ env.POETRY_VERSION }}
        virtualenvs-create: true
        virtualenvs-in-project: true

    - name: Install semantic-release tooling
      run: |
        python -m pip install --upgrade --no-cache-dir "pip==26.1.1"
        python -m pip install --no-cache-dir "python-semantic-release==9.21.1"

    - name: Determine next version with python-semantic-release
      id: get-version
      env:
        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |
        # Use semantic-release to determine next version
        semantic-release version --no-commit --no-tag 2>&1 | tee /tmp/sr-output.txt

        # Extract version from semantic-release output
        # If no commits since last tag, version won't change
        VERSION=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['tool']['poetry']['version'])")
        TAG="v${VERSION}"

        echo "version=${VERSION}" >> $GITHUB_OUTPUT
        echo "tag=${TAG}" >> $GITHUB_OUTPUT
        echo "Version: ${VERSION}, Tag: ${TAG}"

  # Skip tests - they already passed in CI before merge to main and in Main Branch Validation
  # This workflow focuses on building and publishing the release
  validate:
    needs: version-bump
    runs-on: ubuntu-latest
    timeout-minutes: 5
    permissions:
      contents: write  # Need write to create tags
      actions: read    # Needed to verify CodeQL run status
    outputs:
      tag: ${{ steps.check-tag.outputs.tag }}
      version: ${{ steps.check-tag.outputs.version }}
      tag-exists: ${{ steps.check-exists.outputs.exists }}
      pypi-exists: ${{ steps.check-pypi.outputs.exists }}
      docker-exists: ${{ steps.check-docker.outputs.exists }}
      docker-mcp-exists: ${{ steps.check-docker-mcp.outputs.exists }}

    steps:
    - name: Clone trusted repository snapshot
      run: |
        git clone https://github.com/kpeacocke/souschef.git .
        git checkout main
        git fetch --tags --force

    - name: Verify code is from main branch
      env:
        BRANCH: ${{ github.event.workflow_run.head_branch || github.ref_name }}
      run: |
        if [[ -z "$BRANCH" ]] || [[ "$BRANCH" != "main" ]]; then
          printf 'ERROR: Release workflow can only run on main branch, got: %s\n' "$BRANCH" >&2
          exit 1
        fi
        echo "Verified: code is from main branch"

    - name: Ensure CodeQL scan succeeded for this commit (best-effort)
      if: github.event_name == 'workflow_run'
      env:
        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        REPO: kpeacocke/souschef  # Explicitly hardcode repository
        HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
      run: |
        echo "Checking CodeQL status for $HEAD_SHA"
        api_url="https://api.github.com/repos/${REPO}/actions/workflows/codeql.yml/runs?head_sha=${HEAD_SHA}"
        run_json=$(curl -s --proto '=https' -H "Authorization: Bearer ${GH_TOKEN}" -H "Accept: application/vnd.github+json" "$api_url") || true
        conclusion=$(echo "$run_json" | jq -r '.workflow_runs[0].conclusion // "none"')
        if [ "$conclusion" != "success" ]; then
          echo "Warning: CodeQL workflow not successful for $HEAD_SHA (conclusion=$conclusion). Proceeding because Main Branch Validation succeeded."
        else
          echo "CodeQL succeeded for $HEAD_SHA"
        fi

    - name: Set up Python
      uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405  # v6.2.0
      with:
        python-version: ${{ env.PYTHON_VERSION }}

    - name: Use version from semantic versioning
      id: check-tag
      env:
        VERSION: ${{ needs.version-bump.outputs.version }}
        TAG: ${{ needs.version-bump.outputs.tag }}
      run: |
        echo "version=${VERSION}" >> $GITHUB_OUTPUT
        echo "tag=${TAG}" >> $GITHUB_OUTPUT
        echo "Version: ${VERSION}"
        echo "Release tag: ${TAG}"

    - name: Check remote for existing tag
      id: check-exists
      env:
        TAG: ${{ steps.check-tag.outputs.tag }}
      run: |
        if git ls-remote --tags origin "$TAG" | grep -q "refs/tags/${TAG}$"; then
          echo "exists=true" >> $GITHUB_OUTPUT
          echo "Tag $TAG already exists on remote"
        else
          echo "exists=false" >> $GITHUB_OUTPUT
          echo "Tag $TAG does not exist on remote"
        fi

    - name: Check PyPI for existing version
      id: check-pypi
      env:
        VERSION: ${{ steps.check-tag.outputs.version }}
      run: |
        set -e
        exists=$(curl -fsSL --proto '=https' https://pypi.org/pypi/souschef/json | jq -r --arg v "$VERSION" '.releases[$v] | if . == null then "false" else "true" end') || exists=false
        echo "exists=$exists" >> $GITHUB_OUTPUT
        if [ "$exists" = "true" ]; then
          echo "Version $VERSION already exists on PyPI"
        else
          echo "Version $VERSION not found on PyPI"
        fi

    - name: Check Docker registry for existing image
      id: check-docker
      env:
        VERSION: ${{ steps.check-tag.outputs.version }}
        GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |
        # Check if the Docker image with this tag exists in GHCR
        IMAGE="ghcr.io/${{ github.repository_owner }}/souschef:$VERSION"
        if docker manifest inspect "$IMAGE" >/dev/null 2>&1 || \
           curl -fsSL -H "Authorization: Bearer $GHCR_TOKEN" \
           "https://ghcr.io/v2/${{ github.repository_owner }}/souschef/manifests/$VERSION" >/dev/null 2>&1; then
          echo "exists=true" >> $GITHUB_OUTPUT
          echo "Docker image $IMAGE already exists"
        else
          echo "exists=false" >> $GITHUB_OUTPUT
          echo "Docker image $IMAGE not found"
        fi

    - name: Check Docker registry for existing MCP image
      id: check-docker-mcp
      env:
        VERSION: ${{ steps.check-tag.outputs.version }}
        GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |
        # Check if the MCP Docker image with this tag exists in GHCR
        IMAGE="ghcr.io/${{ github.repository_owner }}/souschef-mcp:$VERSION"
        if docker manifest inspect "$IMAGE" >/dev/null 2>&1 || \
           curl -fsSL -H "Authorization: Bearer $GHCR_TOKEN" \
           "https://ghcr.io/v2/${{ github.repository_owner }}/souschef-mcp/manifests/$VERSION" >/dev/null 2>&1; then
          echo "exists=true" >> $GITHUB_OUTPUT
          echo "Docker MCP image $IMAGE already exists"
        else
          echo "exists=false" >> $GITHUB_OUTPUT
          echo "Docker MCP image $IMAGE not found"
        fi

  build-package:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    needs: validate
    # Always build the package - needed for all downstream jobs
    permissions:
      contents: read

    steps:
    - name: Clone trusted repository snapshot
      run: |
        git clone https://github.com/kpeacocke/souschef.git .
        git checkout main
        git fetch --tags --force

    - name: Set up Python
      uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405  # v6.2.0
      with:
        python-version: ${{ env.PYTHON_VERSION }}

    - name: Install Poetry
      uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a  # v1.4.1
      with:
        version: ${{ env.POETRY_VERSION }}
        virtualenvs-create: true
        virtualenvs-in-project: true

    - name: Cache Poetry dependencies
      uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae  # v5.0.5
      with:
        path: .venv
        key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
        restore-keys: |
          ${{ runner.os }}-poetry-

    - name: Install Twine (for package validation)
      run: |
        python -m pip install --upgrade --no-cache-dir "pip==26.1.1"
        python -m pip install --no-cache-dir "twine==6.1.0"

    - name: Set package version to release version
      env:
        VERSION: ${{ needs.validate.outputs.version }}
      run: poetry version "$VERSION"

    - name: Build Python package
      run: poetry build

    - name: Validate package with twine
      run: twine check dist/*

    - name: Upload package artifacts
      uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a  # v7.0.1
      with:
        name: release-packages
        path: dist/
        retention-days: 7

  publish-pypi:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    needs: [validate, build-package, create-release]
    # Only skip if version already exists on PyPI
    if: needs.validate.outputs.pypi-exists != 'true'
    environment:
      name: pypi
      url: https://pypi.org/project/souschef/
    permissions:
      contents: read
      id-token: write  # Required for PyPI Trusted Publishing (OIDC)
      deployments: write  # For deployment status updates

    steps:
    - name: Start deployment
      uses: bobheadxi/deployments@648679e8e4915b27893bd7dbc35cb504dc915bc8  # v1.5.0
      id: deployment
      with:
        step: start
        token: ${{ secrets.GITHUB_TOKEN }}
        env: pypi
        ref: ${{ needs.validate.outputs.tag }}

    - name: Download package artifacts
      uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c  # v8.0.1
      with:
        name: release-packages
        path: dist/

    - name: Publish package to PyPI
      uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b  # v1.14.0
      with:
        attestations: true

    - name: Update deployment status (success)
      uses: bobheadxi/deployments@648679e8e4915b27893bd7dbc35cb504dc915bc8  # v1.5.0
      if: success()
      with:
        step: finish
        token: ${{ secrets.GITHUB_TOKEN }}
        status: ${{ job.status }}
        deployment_id: ${{ steps.deployment.outputs.deployment_id }}
        env: pypi
        env_url: https://pypi.org/project/souschef/${{ needs.validate.outputs.version }}/

    - name: Update deployment status (failure)
      uses: bobheadxi/deployments@648679e8e4915b27893bd7dbc35cb504dc915bc8  # v1.5.0
      if: failure()
      with:
        step: finish
        token: ${{ secrets.GITHUB_TOKEN }}
        status: ${{ job.status }}
        deployment_id: ${{ steps.deployment.outputs.deployment_id }}
        env: pypi

  create-release:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    needs: [validate, build-package]
    # Only skip if tag already exists
    if: needs.validate.outputs.tag-exists != 'true'
    permissions:
      contents: write

    steps:
    - name: Clone trusted repository snapshot (push enabled)
      env:
        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |
        git clone "https://x-access-token:${GH_TOKEN}@github.com/kpeacocke/souschef.git" .
        git checkout main
        git fetch --tags --force

    - name: Create git tag and GitHub release
      env:
        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        TAG: ${{ needs.validate.outputs.tag }}
        VERSION: ${{ needs.validate.outputs.version }}
      run: |
        # Create and push tag (tag main HEAD explicitly)
        git tag "$TAG"
        git push origin "$TAG"

        # Create GitHub release with auto-generated notes
        gh release create "$TAG" \
          --title "Release $VERSION" \
          --generate-notes \
          --latest

    - name: Download package artifacts
      uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c  # v8.0.1
      with:
        name: release-packages
        path: dist/

    - name: Upload packages to GitHub Release
      env:
        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        TAG: ${{ needs.validate.outputs.tag }}
      run: |
        echo "Attaching packages to release $TAG..."
        gh release upload "$TAG" dist/*

  build-and-push-docker-ui:
    runs-on: ubuntu-latest
    needs: [validate, build-package]
    # Always rebuild on successful release runs to pick up base image security updates.
    timeout-minutes: 30
    permissions:
      contents: read
      packages: write
      id-token: write
      deployments: write  # For deployment status updates
    outputs:
      image-digest: ${{ steps.build.outputs.digest }}
      image-pushed: ${{ steps.build.outputs.digest != '' }}

    steps:
      - name: Clone trusted repository snapshot
        run: |
          git clone https://github.com/kpeacocke/souschef.git .
          git checkout main
          git fetch --tags --force

      - name: Start Docker deployment
        uses: bobheadxi/deployments@648679e8e4915b27893bd7dbc35cb504dc915bc8  # v1.5.0
        id: docker-deployment
        with:
          step: start
          token: ${{ secrets.GITHUB_TOKEN }}
          env: docker
          ref: ${{ github.event.workflow_run.head_sha || github.sha }}

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd  # v4.0.0
        with:
          platforms: linux/amd64,linux/arm64
          # Override setup-buildx defaults so insecure entitlements are not enabled.
          buildkitd-flags: ""

      - name: Generate build metadata
        id: meta
        uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf  # v6.0.0
        with:
          images: |
            ghcr.io/${{ github.repository_owner }}/souschef
            docker.io/${{ secrets.DOCKERHUB_USERNAME }}/souschef
          tags: |
            type=semver,pattern={{version}},value=${{ needs.validate.outputs.version }}
            type=semver,pattern={{major}}.{{minor}},value=${{ needs.validate.outputs.version }}
            type=semver,pattern={{major}},value=${{ needs.validate.outputs.version }}
            type=raw,value=latest
          labels: |
            org.opencontainers.image.title=SousChef
            org.opencontainers.image.description=Web UI for SousChef Chef to Ansible converter

      - name: Login to GitHub Container Registry
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121  # v4.1.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Login to Docker Hub
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121  # v4.1.0
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push Docker UI image
        id: build
        uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294  # v7.0.0
        with:
          context: .
          file: Dockerfile.ui
          platforms: linux/amd64,linux/arm64
          pull: true
          no-cache: true
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha,scope=ui
          cache-to: type=gha,mode=max,scope=ui
          build-args: |
            PYTHON_VERSION=3.14.3
            POETRY_VERSION=2.3.4
          provenance: true
          sbom: true

      - name: Verify image digest
        run: |
          DIGEST="${{ steps.build.outputs.digest }}"
          if [ -z "$DIGEST" ]; then
            echo "ERROR: Image digest is empty"
            exit 1
          fi
          echo "Image pushed successfully with digest: $DIGEST"

      - name: Update Docker deployment status (success)
        uses: bobheadxi/deployments@648679e8e4915b27893bd7dbc35cb504dc915bc8  # v1.5.0
        if: success()
        with:
          step: finish
          token: ${{ secrets.GITHUB_TOKEN }}
          status: success
          deployment_id: ${{ steps.docker-deployment.outputs.deployment_id }}
          env: docker
          env_url: https://ghcr.io/${{ github.repository_owner }}/souschef:${{ needs.validate.outputs.version }}

      - name: Update Docker deployment status (failure)
        uses: bobheadxi/deployments@648679e8e4915b27893bd7dbc35cb504dc915bc8  # v1.5.0
        if: failure()
        with:
          step: finish
          token: ${{ secrets.GITHUB_TOKEN }}
          status: failure
          deployment_id: ${{ steps.docker-deployment.outputs.deployment_id }}
          env: docker

  build-and-push-docker-mcp:
    runs-on: ubuntu-latest
    needs: [validate, build-package]
    # Always rebuild on successful release runs to pick up base image security updates.
    timeout-minutes: 90
    permissions:
      contents: read
      packages: write
      id-token: write
      deployments: write
    outputs:
      image-digest: ${{ steps.build.outputs.digest }}
      image-pushed: ${{ steps.build.outputs.digest != '' }}

    steps:
      - name: Clone trusted repository snapshot
        run: |
          git clone https://github.com/kpeacocke/souschef.git .
          git checkout main
          git fetch --tags --force

      - name: Start Docker MCP deployment
        uses: bobheadxi/deployments@648679e8e4915b27893bd7dbc35cb504dc915bc8  # v1.5.0
        id: docker-mcp-deployment
        with:
          step: start
          token: ${{ secrets.GITHUB_TOKEN }}
          env: docker-mcp
          ref: ${{ github.event.workflow_run.head_sha || github.sha }}

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd  # v4.0.0
        with:
          platforms: linux/amd64,linux/arm64
          # Override setup-buildx defaults so insecure entitlements are not enabled.
          buildkitd-flags: ""

      - name: Generate build metadata for MCP
        id: meta
        uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf  # v6.0.0
        with:
          images: |
            ghcr.io/${{ github.repository_owner }}/souschef-mcp
            docker.io/${{ secrets.DOCKERHUB_USERNAME }}/souschef-mcp
          tags: |
            type=semver,pattern={{version}},value=${{ needs.validate.outputs.version }}
            type=semver,pattern={{major}}.{{minor}},value=${{ needs.validate.outputs.version }}
            type=semver,pattern={{major}},value=${{ needs.validate.outputs.version }}
            type=raw,value=latest
          labels: |
            org.opencontainers.image.title=SousChef MCP Server
            org.opencontainers.image.description=MCP server for SousChef Chef to Ansible converter

      - name: Login to GitHub Container Registry
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121  # v4.1.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Login to Docker Hub
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121  # v4.1.0
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push Docker MCP image
        id: build
        uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294  # v7.0.0
        with:
          context: .
          file: Dockerfile
          platforms: linux/amd64,linux/arm64
          pull: true
          no-cache: false
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha,scope=mcp
          cache-to: type=gha,mode=max,scope=mcp
          build-args: |
            PYTHON_VERSION=3.14.3
            POETRY_VERSION=2.3.4
          provenance: true
          sbom: true

      - name: Verify MCP image digest
        run: |
          DIGEST="${{ steps.build.outputs.digest }}"
          if [ -z "$DIGEST" ]; then
            echo "ERROR: MCP image digest is empty"
            exit 1
          fi
          echo "MCP image pushed successfully with digest: $DIGEST"

      - name: Update Docker MCP deployment status (success)
        uses: bobheadxi/deployments@648679e8e4915b27893bd7dbc35cb504dc915bc8  # v1.5.0
        if: success()
        with:
          step: finish
          token: ${{ secrets.GITHUB_TOKEN }}
          status: success
          deployment_id: ${{ steps.docker-mcp-deployment.outputs.deployment_id }}
          env: docker-mcp
          env_url: https://ghcr.io/${{ github.repository_owner }}/souschef-mcp:${{ needs.validate.outputs.version }}

      - name: Update Docker MCP deployment status (failure)
        uses: bobheadxi/deployments@648679e8e4915b27893bd7dbc35cb504dc915bc8  # v1.5.0
        if: failure()
        with:
          step: finish
          token: ${{ secrets.GITHUB_TOKEN }}
          status: failure
          deployment_id: ${{ steps.docker-mcp-deployment.outputs.deployment_id }}
          env: docker-mcp

  scan-docker-image-ui:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    needs: [validate, build-and-push-docker-ui]
    if: needs.build-and-push-docker-ui.result == 'success' && needs.build-and-push-docker-ui.outputs.image-pushed == 'true'
    permissions:
      packages: read
      security-events: write

    steps:
      - name: Login to GitHub Container Registry
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121  # v4.1.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Run Trivy vulnerability scan
        uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25  # v0.36.0
        with:
          image-ref: ghcr.io/${{ github.repository_owner }}/souschef:${{ needs.validate.outputs.version }}
          format: sarif
          output: trivy-results.sarif
          severity: CRITICAL,HIGH
          ignore-unfixed: true
          exit-code: '0'

      - name: Upload Trivy results to GitHub Security tab
        uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13  # v4
        if: always()
        with:
          sarif_file: trivy-results.sarif
          category: trivy-image-scan

      - name: Display vulnerability report
        if: always()
        run: |
          echo "Vulnerability scan results:"
          echo "See GitHub Security tab for detailed report"

  scan-docker-image-mcp:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    needs: [validate, build-and-push-docker-mcp]
    if: needs.build-and-push-docker-mcp.result == 'success' && needs.build-and-push-docker-mcp.outputs.image-pushed == 'true'
    permissions:
      packages: read
      security-events: write

    steps:
      - name: Login to GitHub Container Registry
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121  # v4.1.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Run Trivy vulnerability scan on MCP image
        uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25  # v0.36.0
        with:
          image-ref: ghcr.io/${{ github.repository_owner }}/souschef-mcp:${{ needs.validate.outputs.version }}
          format: sarif
          output: trivy-results-mcp.sarif
          severity: CRITICAL,HIGH
          ignore-unfixed: true
          exit-code: '0'

      - name: Upload Trivy MCP results to GitHub Security tab
        uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13  # v4
        if: always()
        with:
          sarif_file: trivy-results-mcp.sarif
          category: trivy-mcp-image-scan

      - name: Display MCP vulnerability report
        if: always()
        run: |
          echo "MCP vulnerability scan results:"
          echo "See GitHub Security tab for detailed report"

  test-docker-image-ui:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    needs: [validate, build-and-push-docker-ui]
    if: needs.build-and-push-docker-ui.result == 'success' && needs.build-and-push-docker-ui.outputs.image-pushed == 'true'
    permissions:
      packages: read

    steps:
      - name: Login to GitHub Container Registry
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121  # v4.1.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Run container and verify it starts
        run: |
          set -e
          # Pull the image first to ensure it's available
          docker pull "ghcr.io/${{ github.repository_owner }}/souschef:${{ needs.validate.outputs.version }}"

          # Test if container can start (with 10-second timeout)
          timeout 10 docker run --rm \
            -e STREAMLIT_SERVER_HEADLESS=true \
            -e STREAMLIT_BROWSER_GATHER_USAGE_STATS=false \
            "ghcr.io/${{ github.repository_owner }}/souschef:${{ needs.validate.outputs.version }}" \
            /bin/true || true

          echo "Container started successfully"

      - name: Verify health check works
        run: |
          set -e
          # Override the ENTRYPOINT to avoid streamlit run being prepended
          OUTPUT=$(docker run --rm --entrypoint="" \
            "ghcr.io/${{ github.repository_owner }}/souschef:${{ needs.validate.outputs.version }}" \
            python -m souschef.ui.health_check)

          echo "Health check output: $OUTPUT"

          if echo "$OUTPUT" | grep -q "healthy"; then
            echo "Health check passed"
          else
            echo "Health check failed"
            exit 1
          fi

  test-docker-image-mcp:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    needs: [validate, build-and-push-docker-mcp]
    if: needs.build-and-push-docker-mcp.result == 'success' && needs.build-and-push-docker-mcp.outputs.image-pushed == 'true'
    permissions:
      packages: read

    steps:
      - name: Login to GitHub Container Registry
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121  # v4.1.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Pull and verify MCP image
        run: |
          set -e
          docker pull "ghcr.io/${{ github.repository_owner }}/souschef-mcp:${{ needs.validate.outputs.version }}"
          echo "MCP image pulled successfully"

      - name: Test MCP server starts
        run: |
          set -e
          # Test that the MCP server can start and respond
          # Use timeout to prevent hanging, expect it to wait for stdio input
          timeout 5 docker run --rm -i \
            "ghcr.io/${{ github.repository_owner }}/souschef-mcp:${{ needs.validate.outputs.version }}" \
            </dev/null || EXIT_CODE=$?

          # Exit code 124 means timeout (expected for stdio server waiting for input)
          # Exit code 0 means it exited cleanly
          if [ "${EXIT_CODE:-0}" -eq 124 ] || [ "${EXIT_CODE:-0}" -eq 0 ]; then
            echo "MCP server test passed (exit code: ${EXIT_CODE:-0})"
          else
            echo "MCP server test failed with unexpected exit code: ${EXIT_CODE:-0}"
            exit 1
          fi

      - name: Verify MCP entrypoint
        run: |
          set -e
          # Test that the MCP server module is accessible
          docker run --rm --entrypoint="" \
            "ghcr.io/${{ github.repository_owner }}/souschef-mcp:${{ needs.validate.outputs.version }}" \
            python -c "import souschef.server; print('MCP server module verified')"
          echo "MCP entrypoint verified"

  notify-docker-deployment:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    needs: [validate, build-and-push-docker-ui, build-and-push-docker-mcp, scan-docker-image-ui, scan-docker-image-mcp, test-docker-image-ui, test-docker-image-mcp]
    if: |
      always()
      && needs.validate.result == 'success'
      && (needs.build-and-push-docker-ui.result == 'success' || needs.build-and-push-docker-mcp.result == 'success')
      && (needs.scan-docker-image-ui.result == 'success' || needs.scan-docker-image-ui.result == 'skipped')
      && (needs.scan-docker-image-mcp.result == 'success' || needs.scan-docker-image-mcp.result == 'skipped')
      && (needs.test-docker-image-ui.result == 'success' || needs.test-docker-image-ui.result == 'skipped')
      && (needs.test-docker-image-mcp.result == 'success' || needs.test-docker-image-mcp.result == 'skipped')
    permissions:
      contents: read
      issues: write

    steps:
      - name: Create deployment notification issue
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3  # v9.0.0
        with:
          script: |
            const version = "${{ needs.validate.outputs.version }}";
            const registry = "ghcr.io";
            const owner = "${{ github.repository_owner }}";
            const uiDigest = "${{ needs.build-and-push-docker-ui.outputs.image-digest }}";
            const mcpDigest = "${{ needs.build-and-push-docker-mcp.outputs.image-digest }}";
            const tag = "${{ needs.validate.outputs.tag }}";
            const issueTitle = "Docker Images Published: " + tag;

            const versionParts = version.split('.');
            const majorMinor = versionParts.length >= 2
              ? versionParts[0] + "." + versionParts[1]
              : versionParts[0];
            const major = versionParts[0];

            let body = "## Docker Images Published\n\n";

            if (uiDigest) {
              body += "### UI Image (Streamlit)\n\n";
              body += "**Image:** `" + registry + "/" + owner + "/souschef:" + version + "`\n\n";
              body += "**Digest:** " + uiDigest + "\n\n";
              body += "**Pull Commands:**\n";
              body += "```bash\n";
              body += "docker pull " + registry + "/" + owner + "/souschef:" + version + "\n";
              body += "docker pull " + registry + "/" + owner + "/souschef:latest\n";
              body += "```\n\n";
            }

            if (mcpDigest) {
              body += "### MCP Server Image\n\n";
              body += "**Image:** `" + registry + "/" + owner + "/souschef-mcp:" + version + "`\n\n";
              body += "**Digest:** " + mcpDigest + "\n\n";
              body += "**Pull Commands:**\n";
              body += "```bash\n";
              body += "docker pull " + registry + "/" + owner + "/souschef-mcp:" + version + "\n";
              body += "docker pull " + registry + "/" + owner + "/souschef-mcp:latest\n";
              body += "```\n\n";
            }

            body += "**Available Tags:** `" + version + "`, `" + majorMinor + "`, `" + major + "`, `latest`\n\n";
            body += "**Container Registry:** [GitHub Container Registry](https://github.com/${{ github.repository }}/packages)\n\n";
            body += "**Security:** Vulnerability scan results available in [GitHub Security](https://github.com/${{ github.repository }}/security/dependabot)\n\n";
            body += "**Related Release:** " + tag + "\n\n";
            body += "## Build Information\n";
            body += "- **Release Pipeline:** ${{ github.workflow }} #${{ github.run_number }}\n";
            body += "- **Release Workflow Run:** ${{ github.run_id }}\n";
            body += "- **Release Commit:** ${{ github.sha }}";

            const existingIssues = await github.paginate(
              github.rest.issues.listForRepo,
              {
                owner: context.repo.owner,
                repo: context.repo.repo,
                state: 'open',
                per_page: 100,
              }
            );

            const existingIssue = existingIssues.find(
              (issue) => !issue.pull_request && issue.title === issueTitle
            );

            if (existingIssue) {
              await github.rest.issues.update({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: existingIssue.number,
                body: body,
                labels: ['deployment', 'docker', 'release'],
              });

              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: existingIssue.number,
                body: "Release workflow run `${{ github.run_id }}` refreshed this deployment summary.",
              });
            } else {
              await github.rest.issues.create({
                owner: context.repo.owner,
                repo: context.repo.repo,
                title: issueTitle,
                body: body,
                labels: ['deployment', 'docker', 'release'],
              });
            }

  release-complete:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    needs: [validate, build-package, publish-pypi, create-release, build-and-push-docker-ui, build-and-push-docker-mcp, scan-docker-image-ui, scan-docker-image-mcp, test-docker-image-ui, test-docker-image-mcp, notify-docker-deployment]
    if: always()
    permissions:
      contents: read

    steps:
      - name: Release Pipeline Summary
        run: |
          cat > /tmp/summary.md << 'EOF'
          # Release Pipeline Complete

          ## Pipeline Status
          - **Validation:** ${{ needs.validate.result }}
          - **Build Package:** ${{ needs.build-package.result }}
          - **PyPI Publish:** ${{ needs.publish-pypi.result }}
          - **Create Release:** ${{ needs.create-release.result }}
          - **Docker UI Build & Push:** ${{ needs.build-and-push-docker-ui.result }}
          - **Docker MCP Build & Push:** ${{ needs.build-and-push-docker-mcp.result }}
          - **Docker UI Security Scan:** ${{ needs.scan-docker-image-ui.result }}
          - **Docker MCP Security Scan:** ${{ needs.scan-docker-image-mcp.result }}
          - **Docker UI Test:** ${{ needs.test-docker-image-ui.result }}
          - **Docker MCP Test:** ${{ needs.test-docker-image-mcp.result }}
          - **Docker Notification:** ${{ needs.notify-docker-deployment.result }}

          ## Release Information
          - **Version:** ${{ needs.validate.outputs.version || 'N/A' }}
          - **Tag:** ${{ needs.validate.outputs.tag || 'N/A' }}
          - **UI Image Digest:** ${{ needs.build-and-push-docker-ui.outputs.image-digest || 'No image pushed' }}
          - **MCP Image Digest:** ${{ needs.build-and-push-docker-mcp.outputs.image-digest || 'No image pushed' }}

          ## Deployments
          - **PyPI:** https://pypi.org/project/souschef/${{ needs.validate.outputs.version || 'latest' }}/
          - **GitHub Release:** https://github.com/${{ github.repository }}/releases/tag/${{ needs.validate.outputs.tag || 'latest' }}
          echo "- **UI Container Image:** ghcr.io/${{ github.repository_owner }}/souschef:${{ needs.validate.outputs.version || 'latest' }}"
          - **MCP Container Image:** ghcr.io/${{ github.repository_owner }}/souschef-mcp:${{ needs.validate.outputs.version || 'latest' }}

          ## Next Steps
          - PyPI package and Docker images are deployed
          - See GitHub Release for artifacts and release notes
          - See GitHub Security tab for Docker vulnerability scan results
          - Pull the UI image: `docker pull ghcr.io/${{ github.repository_owner }}/souschef:${{ needs.validate.outputs.version || 'latest' }}`
          - Pull the MCP image: `docker pull ghcr.io/${{ github.repository_owner }}/souschef-mcp:${{ needs.validate.outputs.version || 'latest' }}`

          ## Workflow Run Details
          - **Workflow:** ${{ github.workflow }}
          - **Run ID:** ${{ github.run_id }}
          - **Run Number:** ${{ github.run_number }}
          EOF
          cat /tmp/summary.md >> $GITHUB_STEP_SUMMARY

      - name: Report failures
        if: |
          needs.validate.result == 'failure'
          || needs.build-package.result == 'failure'
          || needs.publish-pypi.result == 'failure'
          || needs.create-release.result == 'failure'
          || needs.build-and-push-docker-ui.result == 'failure'
          || needs.build-and-push-docker-mcp.result == 'failure'
          || needs.scan-docker-image-ui.result == 'failure'
          || needs.scan-docker-image-mcp.result == 'failure'
          || needs.test-docker-image-ui.result == 'failure'
          || needs.test-docker-image-mcp.result == 'failure'
        run: |
          echo "### Release Pipeline Failed" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "| Stage | Status |" >> $GITHUB_STEP_SUMMARY
          echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
          echo "| Validation | ${{ needs.validate.result }} |" >> $GITHUB_STEP_SUMMARY
          echo "| Build Package | ${{ needs.build-package.result }} |" >> $GITHUB_STEP_SUMMARY
          echo "| PyPI Publish | ${{ needs.publish-pypi.result }} |" >> $GITHUB_STEP_SUMMARY
          echo "| Create Release | ${{ needs.create-release.result }} |" >> $GITHUB_STEP_SUMMARY
          echo "| Docker UI Build & Push | ${{ needs.build-and-push-docker-ui.result }} |" >> $GITHUB_STEP_SUMMARY
          echo "| Docker MCP Build & Push | ${{ needs.build-and-push-docker-mcp.result }} |" >> $GITHUB_STEP_SUMMARY
          echo "| Docker MCP Build & Push | ${{ needs.build-and-push-docker-mcp.result }} |" >> $GITHUB_STEP_SUMMARY
          echo "| Docker UI Scan | ${{ needs.scan-docker-image-ui.result }} |" >> $GITHUB_STEP_SUMMARY
          echo "| Docker MCP Scan | ${{ needs.scan-docker-image-mcp.result }} |" >> $GITHUB_STEP_SUMMARY
          echo "| Docker UI Test | ${{ needs.test-docker-image-ui.result }} |" >> $GITHUB_STEP_SUMMARY
          echo "| Docker MCP Test | ${{ needs.test-docker-image-mcp.result }} |" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "Please review the failed jobs above."
          exit 1
