name: Main Validation

on:
  schedule:
    - cron: '0 1 * * *'
  workflow_dispatch:
    inputs:
      commit:
        description: Exact 40-character commit SHA to validate
        required: true
        type: string

concurrency:
  group: main-validation-${{ github.event_name == 'workflow_dispatch' && format('receipt-{0}', github.run_id) || format('scheduled-{0}', github.ref) }}
  # An 82-minute lane cancelled by the next commit never reaches a verdict.
  cancel-in-progress: false

permissions:
  actions: read
  contents: read

env:
  NODE_OPTIONS: --max-old-space-size=6144
  BUILD_VITEST_MAX_FORKS: '4'
  BUILD_VITEST_MAX_THREADS: '4'
  BUILD_SCHED_IDLE: '0'
  # Heavy lanes share a host under build.slice's aggregate ceiling instead of
  # queueing inside a timed step, where the wait consumed timeout-minutes.
  BUILD_NO_LOCK: '1'

jobs:
  validate-dispatch-commit:
    name: Validate dispatch commit
    runs-on: [self-hosted, linux, x64, multideal-gate]
    timeout-minutes: 5
    steps:
      - name: Require immutable dispatch SHA
        if: github.event_name == 'workflow_dispatch'
        shell: bash
        run: |
          set -euo pipefail
          commit="${{ inputs.commit }}"
          if [[ ! "$commit" =~ ^[0-9a-f]{40}$ ]]; then
            echo 'workflow_dispatch commit must be an exact 40-character lowercase SHA' >&2
            exit 1
          fi

  tooling-contracts:
    name: Tooling contracts
    needs: [validate-dispatch-commit]
    if: always()
    runs-on: [self-hosted, linux, x64, multideal-gate]
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ inputs.commit || github.sha }}
      - uses: ./.github/actions/setup
        with:
          github-packages-token: ${{ secrets.GH_PKG_READ_TOKEN }}
      - name: Workflow, lane, and journey contracts
        run: pnpm quality:changed:test
      - name: E2E risk and coverage contract
        run: bash apps/web/scripts/cpu-limit.sh pnpm --filter web check:e2e-risk

  typecheck-unit:
    name: Typecheck and unit lane
    needs: [validate-dispatch-commit]
    if: always()
    runs-on: [self-hosted, linux, x64, multideal-gate, debian2]
    timeout-minutes: 45
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ inputs.commit || github.sha }}
      - uses: ./.github/actions/setup
        with:
          github-packages-token: ${{ secrets.GH_PKG_READ_TOKEN }}
      - name: Typecheck
        run: bash apps/web/scripts/cpu-limit.sh pnpm --filter web typecheck
      - name: Unit lane
        run: bash apps/web/scripts/cpu-limit.sh pnpm --filter web test

  test-realpg:
    name: Real-Postgres lanes
    needs: [typecheck-unit]
    if: always()
    runs-on: [self-hosted, linux, x64, multideal-gate, debian2]
    timeout-minutes: 60
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      NEON_API_KEY: ${{ secrets.NEON_CI_API_KEY }}
      NEON_PROJECT_ID: ${{ secrets.NEON_CI_PROJECT_ID }}
      NEON_MAIN_BRANCH_ID: ${{ secrets.NEON_CI_MAIN_BRANCH_ID }}
      SOURCE_DATABASE_URL: ${{ secrets.DATABASE_URL }}
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ inputs.commit || github.sha }}
      - uses: ./.github/actions/setup
        with:
          github-packages-token: ${{ secrets.GH_PKG_READ_TOKEN }}
      - name: Real-Postgres cart lane
        run: bash apps/web/scripts/cpu-limit.sh pnpm --filter web test:cart-realpg
      - name: Real-Postgres migration replay lane
        run: bash apps/web/scripts/cpu-limit.sh bash apps/web/scripts/ci-with-disposable-pg.sh pnpm test:realpg

  test-integration-db:
    name: Integration database lanes
    needs: [test-realpg]
    if: always()
    runs-on: [self-hosted, linux, x64, multideal-gate, debian2]
    timeout-minutes: 60
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      NEON_API_KEY: ${{ secrets.NEON_CI_API_KEY }}
      NEON_PROJECT_ID: ${{ secrets.NEON_CI_PROJECT_ID }}
      NEON_MAIN_BRANCH_ID: ${{ secrets.NEON_CI_MAIN_BRANCH_ID }}
      SOURCE_DATABASE_URL: ${{ secrets.DATABASE_URL }}
      JWT_SECRET: ${{ secrets.JWT_SECRET }}
      E2E_SECRET: ${{ secrets.E2E_SECRET }}
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ inputs.commit || github.sha }}
      - uses: ./.github/actions/setup
        with:
          github-packages-token: ${{ secrets.GH_PKG_READ_TOKEN }}
      - name: E2E harness typecheck
        run: bash apps/web/scripts/cpu-limit.sh pnpm --filter web typecheck:e2e
      - name: Integration database tests
        run: bash apps/web/scripts/cpu-limit.sh bash apps/web/scripts/ci-with-disposable-pg.sh pnpm test:integration-db

  test-integration-http:
    name: Integration HTTP lanes
    needs: [test-integration-db]
    if: always()
    runs-on: [self-hosted, linux, x64, multideal-gate, debian2]
    timeout-minutes: 60
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      NEON_API_KEY: ${{ secrets.NEON_CI_API_KEY }}
      NEON_PROJECT_ID: ${{ secrets.NEON_CI_PROJECT_ID }}
      NEON_MAIN_BRANCH_ID: ${{ secrets.NEON_CI_MAIN_BRANCH_ID }}
      SOURCE_DATABASE_URL: ${{ secrets.DATABASE_URL }}
      JWT_SECRET: ${{ secrets.JWT_SECRET }}
      E2E_SECRET: ${{ secrets.E2E_SECRET }}
      PUBLIC_TURNSTILE_SITE_KEY: ${{ vars.TURNSTILE_SITE_KEY }}
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ inputs.commit || github.sha }}
      - uses: ./.github/actions/setup
        with:
          github-packages-token: ${{ secrets.GH_PKG_READ_TOKEN }}
      - name: Admin languages HTTP integration
        run: bash apps/web/scripts/cpu-limit.sh bash apps/web/scripts/ci-integration-http.sh

  test-oracle:
    name: Oracle database lanes
    needs: [test-integration-http]
    if: always()
    runs-on: [self-hosted, linux, x64, multideal-gate, debian2]
    timeout-minutes: 60
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      NEON_API_KEY: ${{ secrets.NEON_CI_API_KEY }}
      NEON_PROJECT_ID: ${{ secrets.NEON_CI_PROJECT_ID }}
      NEON_MAIN_BRANCH_ID: ${{ secrets.NEON_CI_MAIN_BRANCH_ID }}
      SOURCE_DATABASE_URL: ${{ secrets.DATABASE_URL }}
      CI_WITH_ORACLE_PG: '1'
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ inputs.commit || github.sha }}
      - uses: ./.github/actions/setup
        with:
          github-packages-token: ${{ secrets.GH_PKG_READ_TOKEN }}
      - name: Oracle tests
        run: bash apps/web/scripts/cpu-limit.sh bash apps/web/scripts/ci-with-disposable-pg.sh pnpm test:oracle

  locale-structural-parity:
    name: Locale-pair semantic structural parity (disposable Neon)
    needs: [validate-dispatch-commit]
    if: always()
    runs-on: [self-hosted, linux, x64, multideal-gate, debian1]
    timeout-minutes: 30
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      NEON_API_KEY: ${{ secrets.NEON_CI_API_KEY }}
      NEON_PROJECT_ID: ${{ secrets.NEON_CI_PROJECT_ID }}
      NEON_MAIN_BRANCH_ID: ${{ secrets.NEON_CI_MAIN_BRANCH_ID }}
      SOURCE_DATABASE_URL: ${{ secrets.DATABASE_URL }}
      JWT_SECRET: ${{ secrets.JWT_SECRET }}
      E2E_SECRET: ${{ secrets.E2E_SECRET }}
      PUBLIC_TURNSTILE_SITE_KEY: ${{ vars.TURNSTILE_SITE_KEY }}
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ inputs.commit || github.sha }}
          fetch-depth: 0
      - uses: ./.github/actions/setup
        with:
          github-packages-token: ${{ secrets.GH_PKG_READ_TOKEN }}
      - name: Locale-pair semantic structural parity on disposable seeded Neon branch
        run: bash apps/web/scripts/cpu-limit.sh bash apps/web/scripts/ci-with-disposable-locale-pg.sh pnpm --filter web test:locale-parity

  factory-journeys:
    name: Factory journeys
    needs: [locale-structural-parity]
    if: always()
    runs-on: [self-hosted, linux, x64, multideal-gate, debian1]
    timeout-minutes: 180
    env:
      PORT: '4322'
      BASE_URL: http://127.0.0.1:4322
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      NEON_API_KEY: ${{ secrets.NEON_CI_API_KEY }}
      NEON_PROJECT_ID: ${{ secrets.NEON_CI_PROJECT_ID }}
      NEON_MAIN_BRANCH_ID: ${{ secrets.NEON_CI_FACTORY_BRANCH_ID }}
      SOURCE_DATABASE_URL: ${{ secrets.DATABASE_URL }}
      JWT_SECRET: ${{ secrets.JWT_SECRET }}
      E2E_SECRET: ${{ secrets.E2E_SECRET }}
      PUBLIC_TURNSTILE_SITE_KEY: ${{ vars.TURNSTILE_SITE_KEY }}
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ inputs.commit || github.sha }}
      - uses: ./.github/actions/setup
        with:
          github-packages-token: ${{ secrets.GH_PKG_READ_TOKEN }}
      - working-directory: apps/web
        run: pnpm exec playwright install chromium
      - name: Full factory journey sweep
        run: bash apps/web/scripts/cpu-limit.sh bash apps/web/scripts/ci-with-disposable-factory-pg.sh pnpm test:factory-journeys-certified
      - name: Upload factory Playwright evidence
        if: failure()
        uses: actions/upload-artifact@v7
        with:
          name: factory-journeys-${{ inputs.commit || github.sha }}
          path: |
            apps/web/test-results
            apps/web/playwright-report
          if-no-files-found: warn
          retention-days: 14

  factory-ui-matrix:
    name: Factory UI matrix
    needs: [test-oracle]
    if: always()
    runs-on: [self-hosted, linux, x64, multideal-gate, debian2]
    timeout-minutes: 180
    env:
      PORT: '4322'
      BASE_URL: http://127.0.0.1:4322
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      NEON_API_KEY: ${{ secrets.NEON_CI_API_KEY }}
      NEON_PROJECT_ID: ${{ secrets.NEON_CI_PROJECT_ID }}
      NEON_MAIN_BRANCH_ID: ${{ secrets.NEON_CI_FACTORY_BRANCH_ID }}
      SOURCE_DATABASE_URL: ${{ secrets.DATABASE_URL }}
      JWT_SECRET: ${{ secrets.JWT_SECRET }}
      E2E_SECRET: ${{ secrets.E2E_SECRET }}
      PUBLIC_TURNSTILE_SITE_KEY: ${{ vars.TURNSTILE_SITE_KEY }}
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ inputs.commit || github.sha }}
      - uses: ./.github/actions/setup
        with:
          github-packages-token: ${{ secrets.GH_PKG_READ_TOKEN }}
      - working-directory: apps/web
        run: pnpm exec playwright install chromium
      - name: UI matrix factory sweep
        run: bash apps/web/scripts/cpu-limit.sh bash apps/web/scripts/ci-with-disposable-factory-pg.sh pnpm test:factory-ui-matrix
      - name: Upload factory Playwright evidence
        if: failure()
        uses: actions/upload-artifact@v7
        with:
          name: factory-ui-matrix-${{ inputs.commit || github.sha }}
          path: |
            apps/web/test-results
            apps/web/playwright-report
          if-no-files-found: warn
          retention-days: 14

  a11y:
    name: A11y factory suite
    needs: [factory-ui-matrix]
    if: always()
    runs-on: [self-hosted, linux, x64, multideal-gate, debian2]
    timeout-minutes: 45
    env:
      PORT: '4322'
      BASE_URL: http://127.0.0.1:4322
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      NEON_API_KEY: ${{ secrets.NEON_CI_API_KEY }}
      NEON_PROJECT_ID: ${{ secrets.NEON_CI_PROJECT_ID }}
      NEON_MAIN_BRANCH_ID: ${{ secrets.NEON_CI_FACTORY_BRANCH_ID }}
      SOURCE_DATABASE_URL: ${{ secrets.DATABASE_URL }}
      JWT_SECRET: ${{ secrets.JWT_SECRET }}
      E2E_SECRET: ${{ secrets.E2E_SECRET }}
      PUBLIC_TURNSTILE_SITE_KEY: ${{ vars.TURNSTILE_SITE_KEY }}
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ inputs.commit || github.sha }}
      - uses: ./.github/actions/setup
        with:
          github-packages-token: ${{ secrets.GH_PKG_READ_TOKEN }}
      - working-directory: apps/web
        run: pnpm exec playwright install chromium
      - name: Axe WCAG a11y factory suite
        run: bash apps/web/scripts/cpu-limit.sh bash apps/web/scripts/ci-with-disposable-factory-pg.sh pnpm test:a11y
      - name: Upload a11y Playwright evidence
        if: failure()
        uses: actions/upload-artifact@v7
        with:
          name: a11y-${{ inputs.commit || github.sha }}
          path: |
            apps/web/test-results
            apps/web/playwright-report
          if-no-files-found: warn
          retention-days: 14

  pr-browser:
    name: Changed browser journeys
    needs: [factory-journeys]
    if: always()
    runs-on: [self-hosted, linux, x64, multideal-gate, debian1]
    timeout-minutes: 60
    env:
      PORT: '4322'
      BASE_URL: http://127.0.0.1:4322
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      NEON_API_KEY: ${{ secrets.NEON_CI_API_KEY }}
      NEON_PROJECT_ID: ${{ secrets.NEON_CI_PROJECT_ID }}
      NEON_MAIN_BRANCH_ID: ${{ secrets.NEON_CI_FACTORY_BRANCH_ID }}
      SOURCE_DATABASE_URL: ${{ secrets.DATABASE_URL }}
      JWT_SECRET: ${{ secrets.JWT_SECRET }}
      E2E_SECRET: ${{ secrets.E2E_SECRET }}
      PUBLIC_TURNSTILE_SITE_KEY: ${{ vars.TURNSTILE_SITE_KEY }}
      VALIDATION_COMMIT: ${{ inputs.commit || github.sha }}
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ inputs.commit || github.sha }}
          fetch-depth: 0
      - uses: ./.github/actions/setup
        with:
          github-packages-token: ${{ secrets.GH_PKG_READ_TOKEN }}
      - name: Resolve changed-surface base
        shell: bash
        run: |
          set -euo pipefail
          base="$(git rev-parse "${VALIDATION_COMMIT}^")"
          if [[ ! "$base" =~ ^[0-9a-f]{40}$ ]]; then
            echo "Could not resolve a parent commit for ${VALIDATION_COMMIT}" >&2
            exit 1
          fi
          git cat-file -e "${base}^{commit}"
          git merge-base --is-ancestor "$base" HEAD
          echo "QUALITY_BASE_REF=$base" >> "$GITHUB_ENV"
      - working-directory: apps/web
        run: pnpm exec playwright install chromium
      - name: Affected journey browser lane
        run: bash apps/web/scripts/cpu-limit.sh bash apps/web/scripts/ci-with-disposable-factory-pg.sh pnpm --filter web test:pr-browser

  sweep:
    name: UI sweep ratchet
    needs: [pr-browser]
    if: always()
    runs-on: [self-hosted, linux, x64, multideal-gate, debian1]
    timeout-minutes: 60
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      NEON_API_KEY: ${{ secrets.NEON_CI_API_KEY }}
      NEON_PROJECT_ID: ${{ secrets.NEON_CI_PROJECT_ID }}
      NEON_MAIN_BRANCH_ID: ${{ secrets.NEON_CI_FACTORY_BRANCH_ID }}
      SOURCE_DATABASE_URL: ${{ secrets.DATABASE_URL }}
      JWT_SECRET: ${{ secrets.JWT_SECRET }}
      PUBLIC_TURNSTILE_SITE_KEY: ${{ vars.TURNSTILE_SITE_KEY }}
      E2E_SECRET: ${{ secrets.E2E_SECRET }}
      VALIDATION_COMMIT: ${{ inputs.commit || github.sha }}
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ inputs.commit || github.sha }}
          fetch-depth: 0
      - uses: ./.github/actions/setup
        with:
          github-packages-token: ${{ secrets.GH_PKG_READ_TOKEN }}
      - name: Resolve changed-surface base
        shell: bash
        run: |
          set -euo pipefail
          base="$(git rev-parse "${VALIDATION_COMMIT}^")"
          if [[ ! "$base" =~ ^[0-9a-f]{40}$ ]]; then
            echo "Could not resolve a parent commit for ${VALIDATION_COMMIT}" >&2
            exit 1
          fi
          git cat-file -e "${base}^{commit}"
          git merge-base --is-ancestor "$base" HEAD
          echo "QUALITY_BASE_REF=$base" >> "$GITHUB_ENV"
      - working-directory: apps/web
        run: pnpm exec playwright install chromium
      - working-directory: apps/web
        run: ': > tests/e2e/sweep/findings-log.jsonl; rm -f tests/e2e/sweep/nav-fingerprints.jsonl tests/e2e/sweep/sweep-run-complete.json'
      - id: sweep-changed
        continue-on-error: true
        shell: bash
        run: |
          set +e
          output=$(bash apps/web/scripts/cpu-limit.sh bash apps/web/scripts/ci-with-disposable-factory-pg.sh pnpm --filter web sweep:changed 2>&1)
          status=$?
          printf '%s\n' "$output"
          if grep -Fq 'no application changes — skipping sweep' <<< "$output"; then
            echo 'playwright-ran=false' >> "$GITHUB_OUTPUT"
          else
            echo 'playwright-ran=true' >> "$GITHUB_OUTPUT"
          fi
          exit "$status"
      - if: steps.sweep-changed.outputs.playwright-ran == 'true'
        working-directory: apps/web
        run: node scripts/sweep-nav-consistency.mjs
      - if: steps.sweep-changed.outputs.playwright-ran == 'true'
        working-directory: apps/web
        run: node scripts/sweep-ratchet.mjs --require-log

  smoke:
    name: Playwright smoke
    needs: [validate-dispatch-commit]
    if: always()
    runs-on: [self-hosted, linux, x64, multideal-gate]
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ inputs.commit || github.sha }}
      - uses: ./.github/actions/setup
        with:
          github-packages-token: ${{ secrets.GH_PKG_READ_TOKEN }}
      - name: Verify home, deal detail, login, and cart
        working-directory: apps/web
        run: |
          node --input-type=module <<'EOF'
          import { chromium } from '@playwright/test';

          const baseUrl = 'https://dev.multi.deal';
          const browser = await chromium.launch({ headless: true });
          try {
            const page = await browser.newPage();
            const requireOk = async (path) => {
              const response = await page.goto(`${baseUrl}${path}`, { waitUntil: 'domcontentloaded' });
              if (!response || !response.ok()) throw new Error(`${path} returned ${response?.status() ?? 'no response'}`);
            };

            await requireOk('/');
            await requireOk('/login');
            await requireOk('/cart');
            await requireOk('/deals');
            const href = await page.locator('a[href^="/deals/"]').first().getAttribute('href');
            if (!href) throw new Error('No deal-detail link was found on /deals');
            await requireOk(href);
          } finally {
            await browser.close();
          }
          EOF

  notify-failure:
    name: Alert on main validation failure
    needs:
      [
        test-realpg,
        test-integration-db,
        test-integration-http,
        test-oracle,
        locale-structural-parity,
        factory-journeys,
        factory-ui-matrix,
        a11y,
        pr-browser,
        sweep,
        smoke,
        tooling-contracts,
        validate-dispatch-commit,
        typecheck-unit,
      ]
    if: failure()
    runs-on: [self-hosted, linux, x64, multideal-gate]
    timeout-minutes: 5
    steps:
      - name: Notify Telegram
        env:
          TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
          TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
          SERVER_URL: ${{ github.server_url }}
          REPOSITORY: ${{ github.repository }}
          RUN_ID: ${{ github.run_id }}
          COMMIT_SHA: ${{ inputs.commit || github.sha }}
        run: |
          if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then exit 0; fi
          RUN_URL="${SERVER_URL}/${REPOSITORY}/actions/runs/${RUN_ID}"
          TG_MESSAGE=$(printf "🔴 Main validation failed (main@%s) — fix forward\n%s" "${COMMIT_SHA:0:8}" "$RUN_URL")
          export TG_MESSAGE
          curl -s -X POST \
            "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
            --data-urlencode "text=${TG_MESSAGE}" \
            -d "chat_id=${TELEGRAM_CHAT_ID}" \
            -d "parse_mode=HTML" \
            -d "disable_web_page_preview=true"
