name: uv.lock check

# Verify uv.lock is in sync with pyproject.toml.  Blocking check — PRs
# that modify pyproject.toml without regenerating uv.lock (or vice versa)
# must not merge, because the Docker build's `uv sync --frozen` step will
# fail on a stale lockfile and we'd rather catch it here than in the
# docker-publish workflow on main.
#
# ─────────────────────────────────────────────────────────────────────────
# IMPORTANT: this check runs against the MERGED state, not just your branch
# ─────────────────────────────────────────────────────────────────────────
#
# For `pull_request` events, GitHub checks out `refs/pull/<N>/merge` by
# default — a synthetic commit that merges your PR branch into the CURRENT
# state of `main`.  That means the pyproject.toml evaluated here is
# `main's pyproject.toml + your PR's changes to pyproject.toml`, not just
# what's on your branch.
#
# Failure mode this creates: if `main` has advanced since you branched
# (e.g. someone merged a PR that added a dep to pyproject.toml + its
# corresponding uv.lock entries), your branch's uv.lock is missing those
# new entries.  `uv lock --check` resolves against the merged pyproject
# and sees a lockfile that doesn't cover all the current deps → fails
# with "The lockfile at uv.lock needs to be updated."
#
# This can be confusing: `uv lock --check` passes locally (your branch
# is internally consistent) but fails in CI (merged state isn't).
#
# Fix is to sync your branch with main and regenerate the lockfile:
#
#     git fetch origin main
#     git rebase origin/main      # or merge, whatever the repo prefers
#     uv lock                     # regenerates uv.lock against new pyproject.toml
#     git add uv.lock
#     git commit -m "chore: refresh uv.lock after rebase onto main"
#     git push --force-with-lease # if you rebased
#
# If you also changed pyproject.toml in your PR, `uv lock` handles that
# at the same time — one regeneration covers both your changes and the
# drift from main.
#
# This is the correct behavior!  The check is protecting main's Docker
# build: a post-merge build would see the same merged state and fail
# the same way.  Better to catch it here than after merge.

on:
  push:
    branches: [main]
    paths:
      - "pyproject.toml"
      - "uv.lock"
      - ".github/workflows/uv-lockfile-check.yml"

  # No paths filter — the job must always run so the required check
  # reports a status (path-gated workflows leave checks "pending" forever
  # when no matching files change, which blocks merge).
  pull_request:
    branches: [main]

permissions:
  contents: read

concurrency:
  group: uv-lockfile-check-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
  check:
    name: uv lock --check
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Install uv
        uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5

      # `uv lock --check` re-resolves the project from pyproject.toml and
      # compares the result to uv.lock, exiting non-zero if they disagree.
      # No network writes, no file modifications.
      #
      # On PRs this runs against the merge commit (see comment at the top
      # of this file) — failures often mean "your branch is behind main,
      # rebase and regenerate uv.lock."
      - name: Verify uv.lock is up-to-date
        run: |
          if ! uv lock --check; then
            cat <<'EOF' >> "$GITHUB_STEP_SUMMARY"
          ## ❌ uv.lock is out of sync with pyproject.toml

          **If this is a PR:** this check runs against the merged state
          (your branch + current `main`), not just your branch.  If
          `uv lock --check` passes locally, your branch is likely behind
          `main` — recent changes to `pyproject.toml` on `main` aren't
          reflected in your branch's `uv.lock` yet.

          To fix, sync with main and regenerate the lockfile:

          ```bash
          git fetch origin main
          git rebase origin/main   # or `git merge origin/main`
          uv lock                  # regenerate against new pyproject.toml
          git add uv.lock
          git commit -m "chore: refresh uv.lock after syncing with main"
          git push --force-with-lease  # drop --force-with-lease if you merged
          ```

          **If you only changed pyproject.toml:** run `uv lock` locally
          and commit the result.

          This check is blocking because the Docker image build uses
          `uv sync --frozen --extra all`, which rejects stale lockfiles
          — catching it here avoids a ~15 min failed docker-publish run
          on `main` post-merge.
          EOF
            echo "::error title=uv.lock out of sync::Run \`uv lock\` locally and commit the result. If on a PR, sync with main first."
            exit 1
          fi
