name: PR

on:
  pull_request:
    branches:
      - master

concurrency:
  group: pr-${{ github.event.pull_request.number }}
  cancel-in-progress: true

jobs:
  policy:
    runs-on: ubuntu-latest
    timeout-minutes: 5

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Block manual lockfile edits
        if: github.head_ref != 'chore/refresh-lockfile'
        run: |
          # Diff the PR branch against its merge base so recent base-branch commits
          # do not masquerade as changes made by the PR itself.
          changed="$(git diff --name-only "${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}")"
          if printf '%s\n' "$changed" | grep -qx 'pnpm-lock.yaml'; then
            echo "Do not commit pnpm-lock.yaml in pull requests. CI owns lockfile updates."
            exit 1
          fi

      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9.15.4
          run_install: false

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 24

      - name: Validate Dockerfile deps stage
        run: node ./scripts/check-docker-deps-stage.mjs

      - name: Validate release package manifest
        run: node ./scripts/release-package-map.mjs check

      - name: Verify release package bootstrap for changed manifests
        run: |
          mapfile -t changed_paths < <(git diff --name-only "${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}")
          PAPERCLIP_RELEASE_BOOTSTRAP_BASE_SHA="${{ github.event.pull_request.base.sha }}" \
            node ./scripts/check-release-package-bootstrap.mjs "${changed_paths[@]}"

      - name: Validate dependency resolution when manifests change
        run: |
          changed="$(git diff --name-only "${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}")"
          manifest_pattern='(^|/)package\.json$|^pnpm-workspace\.yaml$|^\.npmrc$|^pnpmfile\.(cjs|js|mjs)$'
          if printf '%s\n' "$changed" | grep -Eq "$manifest_pattern"; then
            pnpm install --lockfile-only --ignore-scripts --no-frozen-lockfile
          fi

  verify:
    needs: [policy]
    runs-on: ubuntu-latest
    timeout-minutes: 20

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9.15.4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 24
          cache: pnpm

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Typecheck workspaces whose build scripts skip TypeScript
        run: pnpm run typecheck:build-gaps

      - name: Run general test suites
        run: pnpm test:run:general

      - name: Verify release registry test coverage
        run: pnpm run test:release-registry

      - name: Build
        run: pnpm build

  verify_serialized_server:
    name: Verify serialized server suites (${{ matrix.shard_label }})
    needs: [policy]
    runs-on: ubuntu-latest
    timeout-minutes: 20
    strategy:
      fail-fast: false
      matrix:
        include:
          - shard_index: 0
            shard_count: 4
            shard_label: 1/4
          - shard_index: 1
            shard_count: 4
            shard_label: 2/4
          - shard_index: 2
            shard_count: 4
            shard_label: 3/4
          - shard_index: 3
            shard_count: 4
            shard_label: 4/4

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9.15.4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 24
          cache: pnpm

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Run serialized server test shard
        run: pnpm test:run:serialized -- --shard-index ${{ matrix.shard_index }} --shard-count ${{ matrix.shard_count }}

  canary_dry_run:
    name: Canary Dry Run
    needs: [policy]
    runs-on: ubuntu-latest
    timeout-minutes: 20

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9.15.4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 24
          cache: pnpm

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      # `release.sh` always executes its Step 2/7 workspace build, even when
      # `--skip-verify` bypasses the initial verification gate.
      - name: Release canary dry run via release.sh internal build
        run: |
          git checkout -B master HEAD
          git checkout -- pnpm-lock.yaml
          ./scripts/release.sh canary --skip-verify --dry-run

  e2e:
    needs: [policy]
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9.15.4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 24
          cache: pnpm

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Install Playwright
        run: npx playwright install --with-deps chromium

      - name: Generate Paperclip config
        run: |
          mkdir -p ~/.paperclip/instances/default
          cat > ~/.paperclip/instances/default/config.json << 'CONF'
          {
            "$meta": { "version": 1, "updatedAt": "2026-01-01T00:00:00.000Z", "source": "onboard" },
            "database": { "mode": "embedded-postgres" },
            "logging": { "mode": "file" },
            "server": { "deploymentMode": "local_trusted", "host": "127.0.0.1", "port": 3100 },
            "auth": { "baseUrlMode": "auto" },
            "storage": { "provider": "local_disk" },
            "secrets": { "provider": "local_encrypted", "strictMode": false }
          }
          CONF

      - name: Run e2e tests
        env:
          PAPERCLIP_E2E_SKIP_LLM: "true"
        run: pnpm run test:e2e

      - name: Upload Playwright report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: |
            tests/e2e/playwright-report/
            tests/e2e/test-results/
          retention-days: 14
