name: QA-Lab - All Lanes

on:
  schedule:
    - cron: "41 4 * * *"
  workflow_dispatch:
    inputs:
      ref:
        description: Ref, tag, or SHA to run
        required: true
        default: main
        type: string
      scenario:
        description: Optional comma-separated Telegram scenario ids
        required: false
        type: string
      discord_scenario:
        description: Optional comma-separated Discord scenario ids
        required: false
        type: string
      whatsapp_scenario:
        description: Optional comma-separated WhatsApp scenario ids
        required: false
        type: string
      slack_scenario:
        description: Optional comma-separated Slack scenario ids
        required: false
        type: string
      matrix_profile:
        description: Matrix QA profile for the live Matrix lane
        required: false
        default: all
        type: choice
        options:
          - fast
          - all
          - transport
          - media
          - e2ee-smoke
          - e2ee-deep
          - e2ee-cli

permissions:
  contents: read
  pull-requests: read

concurrency:
  group: qa-lab-all-lanes-${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
  cancel-in-progress: false

env:
  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
  NODE_VERSION: "24.x"
  OPENCLAW_CI_OPENAI_MODEL: ${{ vars.OPENCLAW_CI_OPENAI_MODEL || 'openai/gpt-5.5' }}
  OPENCLAW_CI_OPENAI_FALLBACK_MODEL: ${{ vars.OPENCLAW_CI_OPENAI_FALLBACK_MODEL || 'openai/gpt-5.4' }}
  OPENCLAW_BUILD_PRIVATE_QA: "1"
  OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"

jobs:
  authorize_actor:
    name: Authorize workflow actor
    runs-on: blacksmith-8vcpu-ubuntu-2404
    outputs:
      authorized: ${{ steps.permission.outputs.authorized }}
    steps:
      - name: Require maintainer-level repository access
        id: permission
        uses: actions/github-script@v8
        with:
          script: |
            if (context.eventName === "schedule") {
              core.info("Scheduled default-branch QA run; actor permission check is only required for manual dispatch.");
              core.setOutput("authorized", "true");
              return;
            }
            const allowed = new Set(["admin", "maintain", "write"]);
            const { owner, repo } = context.repo;
            const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
              owner,
              repo,
              username: context.actor,
            });
            const permission = data.permission;
            core.info(`Actor ${context.actor} permission: ${permission}`);
            if (!allowed.has(permission)) {
              core.notice(
                `Workflow requires write/maintain/admin access. Actor "${context.actor}" has "${permission}".`,
              );
              core.setOutput("authorized", "false");
              return;
            }
            core.setOutput("authorized", "true");

  validate_selected_ref:
    name: Validate selected ref
    needs: authorize_actor
    if: needs.authorize_actor.outputs.authorized == 'true'
    runs-on: blacksmith-8vcpu-ubuntu-2404
    outputs:
      selected_revision: ${{ steps.validate.outputs.selected_revision }}
      trusted_reason: ${{ steps.validate.outputs.trusted_reason }}
    steps:
      - name: Checkout selected ref
        uses: actions/checkout@v6
        with:
          persist-credentials: false
          ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
          fetch-depth: 0

      - name: Validate selected ref
        id: validate
        env:
          GH_TOKEN: ${{ github.token }}
          INPUT_REF: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
        shell: bash
        run: |
          set -euo pipefail
          selected_revision="$(git rev-parse HEAD)"
          trusted_reason=""

          git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main

          if git merge-base --is-ancestor "$selected_revision" refs/remotes/origin/main; then
            trusted_reason="main-ancestor"
          elif git tag --points-at "$selected_revision" | grep -Eq '^v'; then
            trusted_reason="release-tag"
          elif [[ "$INPUT_REF" =~ ^release/[0-9]{4}\.[0-9]+\.[0-9]+$ ]]; then
            git fetch --no-tags origin "+refs/heads/${INPUT_REF}:refs/remotes/origin/${INPUT_REF}"
            release_branch_sha="$(git rev-parse "refs/remotes/origin/${INPUT_REF}")"
            if [[ "$selected_revision" == "$release_branch_sha" ]]; then
              trusted_reason="release-branch-head"
            fi
          else
            pr_head_count="$(
              gh api \
                -H "Accept: application/vnd.github+json" \
                "repos/${GITHUB_REPOSITORY}/commits/${selected_revision}/pulls" \
                --jq '[.[] | select(.state == "open" and .head.repo.full_name == "'"${GITHUB_REPOSITORY}"'" and .head.sha == "'"${selected_revision}"'")] | length'
            )"
            if [[ "$pr_head_count" != "0" ]]; then
              trusted_reason="open-pr-head"
            fi
          fi

          if [[ -z "$trusted_reason" ]]; then
            echo "Ref '${INPUT_REF}' resolved to $selected_revision, which is not trusted for this secret-bearing QA run." >&2
            echo "Allowed refs must be on main, point to a release tag, match a release branch head, or match an open PR head in ${GITHUB_REPOSITORY}." >&2
            exit 1
          fi

          echo "selected_revision=$selected_revision" >> "$GITHUB_OUTPUT"
          echo "trusted_reason=$trusted_reason" >> "$GITHUB_OUTPUT"
          {
            echo "Validated ref: \`${INPUT_REF}\`"
            echo "Resolved SHA: \`$selected_revision\`"
            echo "Trust reason: \`$trusted_reason\`"
          } >> "$GITHUB_STEP_SUMMARY"

  run_mock_parity:
    name: Run QA Lab mock parity lane
    needs: [validate_selected_ref]
    runs-on: blacksmith-8vcpu-ubuntu-2404
    timeout-minutes: 30
    env:
      QA_PARITY_CONCURRENCY: "1"
      OPENCLAW_QA_TRANSPORT_READY_TIMEOUT_MS: "180000"
      OPENAI_API_KEY: ""
      ANTHROPIC_API_KEY: ""
      OPENCLAW_LIVE_OPENAI_KEY: ""
      OPENCLAW_LIVE_ANTHROPIC_KEY: ""
      OPENCLAW_LIVE_GEMINI_KEY: ""
      OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ""
    steps:
      - name: Checkout selected ref
        uses: actions/checkout@v6
        with:
          persist-credentials: false
          ref: ${{ needs.validate_selected_ref.outputs.selected_revision }}
          fetch-depth: 1

      - name: Setup Node environment
        uses: ./.github/actions/setup-node-env
        with:
          node-version: ${{ env.NODE_VERSION }}
          install-bun: "true"

      - name: Build private QA runtime
        env:
          NODE_OPTIONS: --max-old-space-size=8192
        run: pnpm build

      - name: Run OpenAI candidate lane
        run: |
          pnpm openclaw qa suite \
            --provider-mode mock-openai \
            --parity-pack agentic \
            --concurrency "${QA_PARITY_CONCURRENCY}" \
            --model "${OPENCLAW_CI_OPENAI_MODEL}" \
            --alt-model openai/gpt-5.5-alt \
            --output-dir .artifacts/qa-e2e/openai-candidate

      - name: Run Opus 4.8 lane
        run: |
          pnpm openclaw qa suite \
            --provider-mode mock-openai \
            --parity-pack agentic \
            --concurrency "${QA_PARITY_CONCURRENCY}" \
            --model anthropic/claude-opus-4-8 \
            --alt-model anthropic/claude-sonnet-4-6 \
            --output-dir .artifacts/qa-e2e/anthropic-baseline

      - name: Generate parity report
        run: |
          pnpm openclaw qa parity-report \
            --repo-root . \
            --candidate-summary .artifacts/qa-e2e/openai-candidate/qa-suite-summary.json \
            --baseline-summary .artifacts/qa-e2e/anthropic-baseline/qa-suite-summary.json \
            --candidate-label "${OPENCLAW_CI_OPENAI_MODEL}" \
            --baseline-label anthropic/claude-opus-4-8 \
            --output-dir .artifacts/qa-e2e/parity

      - name: Upload parity artifacts
        if: always()
        uses: actions/upload-artifact@v7
        with:
          name: qa-parity-${{ github.run_id }}-${{ github.run_attempt }}
          path: .artifacts/qa-e2e/
          retention-days: 14
          if-no-files-found: warn

  run_live_runtime_token_efficiency:
    name: Run live runtime token-efficiency lane
    needs: [authorize_actor, validate_selected_ref]
    if: github.event_name == 'schedule'
    runs-on: blacksmith-8vcpu-ubuntu-2404
    timeout-minutes: 45
    environment: qa-live-shared
    env:
      QA_PARITY_CONCURRENCY: "1"
      OPENCLAW_QA_TRANSPORT_READY_TIMEOUT_MS: "180000"
      OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
    steps:
      - name: Checkout selected ref
        uses: actions/checkout@v6
        with:
          persist-credentials: false
          ref: ${{ needs.validate_selected_ref.outputs.selected_revision }}
          fetch-depth: 1

      - name: Setup Node environment
        uses: ./.github/actions/setup-node-env
        with:
          node-version: ${{ env.NODE_VERSION }}
          install-bun: "true"

      - name: Validate required QA credential env
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        shell: bash
        run: |
          set -euo pipefail

          if [[ -z "${OPENAI_API_KEY:-}" ]]; then
            echo "Missing required OPENAI_API_KEY." >&2
            exit 1
          fi

      - name: Build private QA runtime
        env:
          NODE_OPTIONS: --max-old-space-size=8192
        run: pnpm build

      - name: Run live runtime parity lane
        id: run_lane
        shell: bash
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          OPENCLAW_LIVE_OPENAI_KEY: ${{ secrets.OPENAI_API_KEY }}
        run: |
          set -euo pipefail

          output_dir=".artifacts/qa-e2e/runtime-token-efficiency-live-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
          echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"

          pnpm openclaw qa suite \
            --repo-root . \
            --provider-mode live-frontier \
            --runtime-parity-tier standard \
            --runtime-parity-tier live-only \
            --concurrency "${QA_PARITY_CONCURRENCY}" \
            --model "${OPENCLAW_CI_OPENAI_MODEL}" \
            --alt-model "${OPENCLAW_CI_OPENAI_FALLBACK_MODEL}" \
            --runtime-pair openclaw,codex \
            --fast \
            --allow-failures \
            --output-dir "${output_dir}/runtime-suite"

      - name: Generate live runtime token-efficiency report
        if: always() && steps.run_lane.outcome != 'skipped' && steps.run_lane.outcome != 'cancelled'
        shell: bash
        run: |
          set -euo pipefail

          pnpm openclaw qa parity-report \
            --repo-root . \
            --runtime-axis \
            --token-efficiency \
            --summary "${{ steps.run_lane.outputs.output_dir }}/runtime-suite/qa-suite-summary.json" \
            --output-dir "${{ steps.run_lane.outputs.output_dir }}/runtime-report"

      - name: Upload live runtime token-efficiency artifacts
        if: always()
        uses: actions/upload-artifact@v7
        with:
          name: qa-live-runtime-token-efficiency-${{ github.run_id }}-${{ github.run_attempt }}
          path: ${{ steps.run_lane.outputs.output_dir }}
          retention-days: 14
          if-no-files-found: warn

  run_live_matrix:
    name: Run Matrix live QA lane
    needs: [authorize_actor, validate_selected_ref]
    if: ${{ !(github.event_name == 'workflow_dispatch' && inputs.matrix_profile == 'all') }}
    runs-on: blacksmith-8vcpu-ubuntu-2404
    timeout-minutes: 60
    environment: qa-live-shared
    steps:
      - name: Checkout selected ref
        uses: actions/checkout@v6
        with:
          persist-credentials: false
          ref: ${{ needs.validate_selected_ref.outputs.selected_revision }}
          fetch-depth: 1

      - name: Setup Node environment
        uses: ./.github/actions/setup-node-env
        with:
          node-version: ${{ env.NODE_VERSION }}
          install-bun: "true"

      - name: Validate required QA credential env
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        shell: bash
        run: |
          set -euo pipefail

          if [[ -z "${OPENAI_API_KEY:-}" ]]; then
            echo "Missing required OPENAI_API_KEY." >&2
            exit 1
          fi

      - name: Build private QA runtime
        env:
          NODE_OPTIONS: --max-old-space-size=8192
        run: pnpm build

      - name: Run Matrix live lane
        id: run_lane
        shell: bash
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          INPUT_MATRIX_PROFILE: ${{ github.event_name == 'workflow_dispatch' && inputs.matrix_profile || 'fast' }}
          OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
          OPENCLAW_QA_MATRIX_NO_REPLY_WINDOW_MS: "3000"
        run: |
          set -euo pipefail

          output_dir=".artifacts/qa-e2e/matrix-live-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
          echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"

          matrix_args=(
            --repo-root . \
            --output-dir "${output_dir}" \
            --provider-mode live-frontier \
            --model "${OPENCLAW_CI_OPENAI_MODEL}" \
            --alt-model "${OPENCLAW_CI_OPENAI_FALLBACK_MODEL}" \
            --profile "${INPUT_MATRIX_PROFILE}" \
            --fast
          )
          if pnpm openclaw qa matrix --help 2>/dev/null | grep -F -q -- "--fail-fast"; then
            matrix_args+=(--fail-fast)
          fi

          pnpm openclaw qa matrix "${matrix_args[@]}"

      - name: Upload Matrix QA artifacts
        if: always()
        uses: actions/upload-artifact@v7
        with:
          name: qa-live-matrix-${{ github.run_id }}-${{ github.run_attempt }}
          path: ${{ steps.run_lane.outputs.output_dir }}
          retention-days: 14
          if-no-files-found: warn

  run_live_matrix_sharded:
    name: Run Matrix live QA lane (${{ matrix.profile }})
    needs: [authorize_actor, validate_selected_ref]
    if: ${{ github.event_name == 'workflow_dispatch' && inputs.matrix_profile == 'all' }}
    runs-on: blacksmith-8vcpu-ubuntu-2404
    timeout-minutes: 60
    environment: qa-live-shared
    strategy:
      fail-fast: false
      matrix:
        profile:
          - transport
          - media
          - e2ee-smoke
          - e2ee-deep
          - e2ee-cli
    steps:
      - name: Checkout selected ref
        uses: actions/checkout@v6
        with:
          persist-credentials: false
          ref: ${{ needs.validate_selected_ref.outputs.selected_revision }}
          fetch-depth: 1

      - name: Setup Node environment
        uses: ./.github/actions/setup-node-env
        with:
          node-version: ${{ env.NODE_VERSION }}
          install-bun: "true"

      - name: Validate required QA credential env
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        shell: bash
        run: |
          set -euo pipefail

          if [[ -z "${OPENAI_API_KEY:-}" ]]; then
            echo "Missing required OPENAI_API_KEY." >&2
            exit 1
          fi

      - name: Build private QA runtime
        env:
          NODE_OPTIONS: --max-old-space-size=8192
        run: pnpm build

      - name: Run Matrix live lane shard
        id: run_lane
        shell: bash
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
          OPENCLAW_QA_MATRIX_NO_REPLY_WINDOW_MS: "3000"
        run: |
          set -euo pipefail

          output_dir=".artifacts/qa-e2e/matrix-live-${{ matrix.profile }}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
          echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"

          matrix_args=(
            --repo-root . \
            --output-dir "${output_dir}" \
            --provider-mode live-frontier \
            --model "${OPENCLAW_CI_OPENAI_MODEL}" \
            --alt-model "${OPENCLAW_CI_OPENAI_FALLBACK_MODEL}" \
            --profile "${{ matrix.profile }}" \
            --fast
          )
          if pnpm openclaw qa matrix --help 2>/dev/null | grep -F -q -- "--fail-fast"; then
            matrix_args+=(--fail-fast)
          fi

          pnpm openclaw qa matrix "${matrix_args[@]}"

      - name: Upload Matrix QA shard artifacts
        if: always()
        uses: actions/upload-artifact@v7
        with:
          name: qa-live-matrix-${{ matrix.profile }}-${{ github.run_id }}-${{ github.run_attempt }}
          path: ${{ steps.run_lane.outputs.output_dir }}
          retention-days: 14
          if-no-files-found: warn

  run_live_telegram:
    name: Run Telegram live QA lane with Convex leases
    needs: [authorize_actor, validate_selected_ref]
    runs-on: blacksmith-8vcpu-ubuntu-2404
    timeout-minutes: 60
    environment: qa-live-shared
    steps:
      - name: Checkout selected ref
        uses: actions/checkout@v6
        with:
          persist-credentials: false
          ref: ${{ needs.validate_selected_ref.outputs.selected_revision }}
          fetch-depth: 1

      - name: Setup Node environment
        uses: ./.github/actions/setup-node-env
        with:
          node-version: ${{ env.NODE_VERSION }}
          install-bun: "true"

      - name: Validate required QA credential env
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
          OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
        shell: bash
        run: |
          set -euo pipefail

          require_var() {
            local key="$1"
            if [[ -z "${!key:-}" ]]; then
              echo "Missing required ${key}." >&2
              exit 1
            fi
          }

          require_var OPENAI_API_KEY
          require_var OPENCLAW_QA_CONVEX_SITE_URL
          require_var OPENCLAW_QA_CONVEX_SECRET_CI

      - name: Build private QA runtime
        env:
          NODE_OPTIONS: --max-old-space-size=8192
        run: pnpm build

      - name: Run Telegram live lane
        id: run_lane
        shell: bash
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
          OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
          OPENCLAW_QA_CREDENTIAL_ACQUIRE_TIMEOUT_MS: "1800000"
          OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
          OPENCLAW_QA_TELEGRAM_CAPTURE_CONTENT: "1"
          INPUT_SCENARIO: ${{ github.event_name == 'workflow_dispatch' && inputs.scenario || '' }}
        run: |
          set -euo pipefail

          output_dir=".artifacts/qa-e2e/telegram-live-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
          scenario_args=()

          if [[ -n "${INPUT_SCENARIO// }" ]]; then
            IFS=',' read -r -a raw_scenarios <<<"${INPUT_SCENARIO}"
            for raw in "${raw_scenarios[@]}"; do
              scenario="$(printf '%s' "${raw}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
              if [[ -n "${scenario}" ]]; then
                scenario_args+=(--scenario "${scenario}")
              fi
            done
          fi

          echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"

          pnpm openclaw qa telegram \
            --repo-root . \
            --output-dir "${output_dir}" \
            --provider-mode live-frontier \
            --model "${OPENCLAW_CI_OPENAI_MODEL}" \
            --alt-model "${OPENCLAW_CI_OPENAI_FALLBACK_MODEL}" \
            --fast \
            --credential-source convex \
            --credential-role ci \
            "${scenario_args[@]}"

      - name: Upload Telegram QA artifacts
        if: always()
        uses: actions/upload-artifact@v7
        with:
          name: qa-live-telegram-${{ github.run_id }}-${{ github.run_attempt }}
          path: ${{ steps.run_lane.outputs.output_dir }}
          retention-days: 14
          if-no-files-found: warn

  run_live_discord:
    name: Run Discord live QA lane with Convex leases
    needs: [authorize_actor, validate_selected_ref]
    runs-on: blacksmith-8vcpu-ubuntu-2404
    timeout-minutes: 60
    environment: qa-live-shared
    steps:
      - name: Checkout selected ref
        uses: actions/checkout@v6
        with:
          persist-credentials: false
          ref: ${{ needs.validate_selected_ref.outputs.selected_revision }}
          fetch-depth: 1

      - name: Setup Node environment
        uses: ./.github/actions/setup-node-env
        with:
          node-version: ${{ env.NODE_VERSION }}
          install-bun: "true"

      - name: Validate required QA credential env
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
          OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
        shell: bash
        run: |
          set -euo pipefail

          require_var() {
            local key="$1"
            if [[ -z "${!key:-}" ]]; then
              echo "Missing required ${key}." >&2
              exit 1
            fi
          }

          require_var OPENAI_API_KEY
          require_var OPENCLAW_QA_CONVEX_SITE_URL
          require_var OPENCLAW_QA_CONVEX_SECRET_CI

      - name: Build private QA runtime
        env:
          NODE_OPTIONS: --max-old-space-size=8192
        run: pnpm build

      - name: Run Discord live lane
        id: run_lane
        shell: bash
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
          OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
          OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
          OPENCLAW_QA_DISCORD_CAPTURE_CONTENT: "1"
          INPUT_SCENARIO: ${{ github.event_name == 'workflow_dispatch' && inputs.discord_scenario || '' }}
        run: |
          set -euo pipefail

          output_dir=".artifacts/qa-e2e/discord-live-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
          scenario_args=()

          if [[ -n "${INPUT_SCENARIO// }" ]]; then
            IFS=',' read -r -a raw_scenarios <<<"${INPUT_SCENARIO}"
            for raw in "${raw_scenarios[@]}"; do
              scenario="$(printf '%s' "${raw}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
              if [[ -n "${scenario}" ]]; then
                scenario_args+=(--scenario "${scenario}")
              fi
            done
          fi

          echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"

          pnpm openclaw qa discord \
            --repo-root . \
            --output-dir "${output_dir}" \
            --provider-mode live-frontier \
            --model openai/gpt-5.5 \
            --alt-model "${OPENCLAW_CI_OPENAI_FALLBACK_MODEL}" \
            --fast \
            --credential-source convex \
            --credential-role ci \
            "${scenario_args[@]}"

      - name: Upload Discord QA artifacts
        if: always()
        uses: actions/upload-artifact@v7
        with:
          name: qa-live-discord-${{ github.run_id }}-${{ github.run_attempt }}
          path: ${{ steps.run_lane.outputs.output_dir }}
          retention-days: 14
          if-no-files-found: warn

  run_live_whatsapp:
    name: Run WhatsApp live QA lane with Convex leases
    needs: [authorize_actor, validate_selected_ref]
    runs-on: blacksmith-8vcpu-ubuntu-2404
    timeout-minutes: 60
    concurrency:
      group: qa-live-whatsapp-shared
      cancel-in-progress: false
    environment: qa-live-shared
    steps:
      - name: Checkout selected ref
        uses: actions/checkout@v6
        with:
          persist-credentials: false
          ref: ${{ needs.validate_selected_ref.outputs.selected_revision }}
          fetch-depth: 1

      - name: Setup Node environment
        uses: ./.github/actions/setup-node-env
        with:
          node-version: ${{ env.NODE_VERSION }}
          install-bun: "true"

      - name: Validate required QA credential env
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
          OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
        shell: bash
        run: |
          set -euo pipefail

          require_var() {
            local key="$1"
            if [[ -z "${!key:-}" ]]; then
              echo "Missing required ${key}." >&2
              exit 1
            fi
          }

          require_var OPENAI_API_KEY
          require_var OPENCLAW_QA_CONVEX_SITE_URL
          require_var OPENCLAW_QA_CONVEX_SECRET_CI

      - name: Build private QA runtime
        env:
          NODE_OPTIONS: --max-old-space-size=8192
        run: pnpm build

      - name: Run WhatsApp live lane
        id: run_lane
        shell: bash
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
          OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
          OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
          OPENCLAW_QA_WHATSAPP_CAPTURE_CONTENT: "1"
          INPUT_SCENARIO: ${{ github.event_name == 'workflow_dispatch' && inputs.whatsapp_scenario || '' }}
        run: |
          set -euo pipefail

          output_dir=".artifacts/qa-e2e/whatsapp-live-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
          scenario_args=()

          if [[ -n "${INPUT_SCENARIO// }" ]]; then
            IFS=',' read -r -a raw_scenarios <<<"${INPUT_SCENARIO}"
            for raw in "${raw_scenarios[@]}"; do
              scenario="$(printf '%s' "${raw}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
              if [[ -n "${scenario}" ]]; then
                scenario_args+=(--scenario "${scenario}")
              fi
            done
          fi

          echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"

          pnpm openclaw qa whatsapp \
            --repo-root . \
            --output-dir "${output_dir}" \
            --provider-mode live-frontier \
            --model "${OPENCLAW_CI_OPENAI_MODEL}" \
            --alt-model "${OPENCLAW_CI_OPENAI_FALLBACK_MODEL}" \
            --fast \
            --credential-source convex \
            --credential-role ci \
            "${scenario_args[@]}"

      - name: Upload WhatsApp QA artifacts
        if: always()
        uses: actions/upload-artifact@v7
        with:
          name: qa-live-whatsapp-${{ github.run_id }}-${{ github.run_attempt }}
          path: ${{ steps.run_lane.outputs.output_dir }}
          retention-days: 14
          if-no-files-found: warn

  run_live_slack:
    name: Run Slack live QA lane with Convex leases
    needs: [authorize_actor, validate_selected_ref]
    runs-on: blacksmith-8vcpu-ubuntu-2404
    timeout-minutes: 60
    environment: qa-live-shared
    steps:
      - name: Checkout selected ref
        uses: actions/checkout@v6
        with:
          persist-credentials: false
          ref: ${{ needs.validate_selected_ref.outputs.selected_revision }}
          fetch-depth: 1

      - name: Setup Node environment
        uses: ./.github/actions/setup-node-env
        with:
          node-version: ${{ env.NODE_VERSION }}
          install-bun: "true"

      - name: Validate required QA credential env
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
          OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
        shell: bash
        run: |
          set -euo pipefail

          require_var() {
            local key="$1"
            if [[ -z "${!key:-}" ]]; then
              echo "Missing required ${key}." >&2
              exit 1
            fi
          }

          require_var OPENAI_API_KEY
          require_var OPENCLAW_QA_CONVEX_SITE_URL
          require_var OPENCLAW_QA_CONVEX_SECRET_CI

      - name: Build private QA runtime
        env:
          NODE_OPTIONS: --max-old-space-size=8192
        run: pnpm build

      - name: Run Slack live lane
        id: run_lane
        shell: bash
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
          OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
          OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
          OPENCLAW_QA_SLACK_CAPTURE_CONTENT: "1"
          INPUT_SCENARIO: ${{ github.event_name == 'workflow_dispatch' && inputs.slack_scenario || '' }}
        run: |
          set -euo pipefail

          output_dir=".artifacts/qa-e2e/slack-live-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
          scenario_args=()

          if [[ -n "${INPUT_SCENARIO// }" ]]; then
            IFS=',' read -r -a raw_scenarios <<<"${INPUT_SCENARIO}"
            for raw in "${raw_scenarios[@]}"; do
              scenario="$(printf '%s' "${raw}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
              if [[ -n "${scenario}" ]]; then
                scenario_args+=(--scenario "${scenario}")
              fi
            done
          fi

          echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"

          pnpm openclaw qa slack \
            --repo-root . \
            --output-dir "${output_dir}" \
            --provider-mode live-frontier \
            --model "${OPENCLAW_CI_OPENAI_MODEL}" \
            --alt-model "${OPENCLAW_CI_OPENAI_FALLBACK_MODEL}" \
            --fast \
            --credential-source convex \
            --credential-role ci \
            "${scenario_args[@]}"

      - name: Upload Slack QA artifacts
        if: always()
        uses: actions/upload-artifact@v7
        with:
          name: qa-live-slack-${{ github.run_id }}-${{ github.run_attempt }}
          path: ${{ steps.run_lane.outputs.output_dir }}
          retention-days: 14
          if-no-files-found: warn
