# qa-review — non-author APPROVE from the `qa` Gitea team required to merge.
#
# RFC#324 Step 1 of 5 (workflow-add). Pairs with `security-review.yml` and the
# branch-protection flip in Step 2.
#
# === DESIGN (RFC#324 v1.1 addendum) ===
#
# A1-α (refire mechanism):
#   Triggers on:
#     - `pull_request_target`: opened, synchronize, reopened
#         → initial status posts when PR opens / re-pushes
#     - comment refires are handled by `review-refire-comments.yml`
#         → a single issue_comment dispatcher prevents every SOP/review
#           comment from enqueueing separate qa/security/tier jobs on
#           Gitea 1.22.6 before job-level `if:` can skip them.
#   Workflow name = `qa-review` ; job name = `approved`.
#   The job's own pass/fail conclusion publishes the status context
#   `qa-review / approved (<event>)` — NO `POST /statuses` call → NO
#   write:repository token scope needed. Sidesteps internal#321 defect #2.
#
# A1.1 (privilege check on slash-comment — INFORMATIONAL ONLY, NOT a gate):
#   The `issue_comment` event fires for ANY commenter, including
#   non-collaborators. The original (v1.2) design gated the eval step
#   behind a collaborator probe → if a non-collaborator commented
#   /qa-recheck, the eval was `if:`-skipped → the job exited 0 anyway →
#   the status context published `success` with ZERO real APPROVE.
#   That was a fail-open: any visitor could green the gate.
#
#   RFC#324 v1.3 §A1.1 correction (option b per hongming-pc 1421):
#   drop privilege-gating of the evaluation entirely. The eval is
#   read-only and idempotent — it reads `pulls/{N}/reviews` and
#   `teams/{id}/members/{u}` (both API-side state that a commenter can't
#   change). Re-running it on a non-collaborator's comment is harmless
#   AND correct: if a real team-member APPROVE exists, the eval flips
#   green; if not, it stays red.
#
#   We KEEP the privilege step as a `::notice::` log line only — useful
#   for griefer-spotting (one operator spamming /recheck) without
#   touching the gate. If rate-limiting is needed later, add it as a
#   separate concern (time-window throttle, not a privilege gate).
#
#   We MUST NOT use `github.event.comment.author_association` (the
#   field doesn't exist on Gitea 1.22.6 webhook payload — this was
#   sop-tier-refire's defect #1).
#
# A4 (no PR-head checkout under pull_request_target):
#   We check out the BASE ref explicitly so the review-check.sh script is
#   loaded from trusted source. We NEVER use `ref: ${{ github.event.pull_request.head.sha }}`.
#   No PR-head code is executed in the runner. Trust boundary preserved.
#
# A5 (real Gitea team):
#   `qa` team (id=20) verified by orchestrator preflight 2026-05-11; queried
#   at run time via /api/v1/teams/20/members/{login}.
#
# === TOKEN ===
#
# The workflow reads PR state, PR reviews, and team membership.
# Gitea 1.22.6's /api/v1/teams/{id}/members/{u} returns 403 ('Must be a
# team member') for tokens whose owner is not in that team. The default
# `secrets.GITHUB_TOKEN` is owned by a workflow-scoped identity that is
# also not in qa/security teams → also 403.
#
# Resolution: a dedicated `RFC_324_TEAM_READ_TOKEN` secret, owned by an
# identity that IS in both `qa` and `security` teams (Owners-tier
# claude-ceo-assistant, or a new service-bot added to both teams).
# Provisioning of this secret is tracked as a follow-up issue (filed by
# core-devops at PR open).
#
# Until that secret is provisioned, the job will exit 1 with a clear
# 403-on-team-probe error and the `qa-review / approved` status will
# stay `failure`. This is the correct fail-closed behavior — the gate
# blocks merge until both (a) a QA team member APPROVEs and (b) the
# workflow has a token that can confirm their team membership.
#
# === SLASH-COMMAND CONTRACT ===
#
#   /qa-recheck   — re-evaluate the gate (e.g. after an APPROVE lands)
#
# Open to any PR commenter. The eval is read-only and idempotent, so
# unprivileged refires are harmless (RFC#324 v1.3 §A1.1). Collaborator
# status is logged for griefer-spotting but does NOT gate execution.

name: qa-review

on:
  pull_request_target:
    types: [opened, synchronize, reopened]

permissions:
  contents: read
  pull-requests: read
  secrets: read

jobs:
  # bp-exempt: PR review bot signal; required merge state is enforced by CI / all-required.
  approved:
    # Gate the job:
    #   - On pull_request_target events: always run.
    # Comment-triggered refires live in review-refire-comments.yml. Keeping
    # this workflow PR-only avoids comment-triggered queue storms.
    if: |
      github.event_name == 'pull_request_target'
    runs-on: ubuntu-latest
    steps:
      - name: Privilege check (A1.1 — INFORMATIONAL log only, NOT a gate)
        # RFC#324 v1.3 §A1.1: this step does NOT gate subsequent steps.
        # It exists solely as a log line for griefer-spotting (one
        # operator spamming /qa-recheck without merit). Re-running the
        # read-only eval on a non-collaborator comment is harmless;
        # gating it would be fail-open (skipped steps still publish
        # `success` for the job's status context).
        # Only runs on issue_comment events; pull_request_target has
        # no comment.user.login so the step is a no-op skip there.
        if: github.event_name == 'issue_comment'
        env:
          GITEA_TOKEN: ${{ secrets.SOP_TIER_CHECK_TOKEN || secrets.GITHUB_TOKEN }}
        run: |
          set -euo pipefail
          login="${{ github.event.comment.user.login }}"
          # Write token to a mode-600 file so it never appears in curl's argv.
          # (#541: -H "Authorization: token $TOKEN" puts the secret in /proc/<pid>/cmdline)
          authfile=$(mktemp)
          chmod 600 "$authfile"
          printf 'header = "Authorization: token %s"\n' "$GITEA_TOKEN" > "$authfile"
          code=$(curl -sS -o /dev/null -w '%{http_code}' -K "$authfile" \
            "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/collaborators/${login}")
          rm -f "$authfile"
          if [ "$code" = "204" ]; then
            echo "::notice::Recheck from ${login} (collaborator=true)"
          else
            echo "::notice::Recheck from ${login} (collaborator=false, HTTP ${code}) — proceeding with read-only eval anyway"
          fi

      - name: Check out BASE ref (A4 — never PR-head)
        # Loads the review-check.sh script from a trusted ref. For
        # pull_request_target the default checkout is BASE already; we
        # set ref explicitly for the issue_comment event too so the
        # script source is always the default-branch version.
        # NEVER use ref: ${{ github.event.pull_request.head.sha }} —
        # that would execute PR-head code with secrets-context.
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2
        with:
          ref: ${{ github.event.repository.default_branch }}

      - name: Evaluate qa-review
        env:
          GITEA_TOKEN: ${{ secrets.SOP_TIER_CHECK_TOKEN || secrets.GITHUB_TOKEN }}
          GITEA_HOST: git.moleculesai.app
          REPO: ${{ github.repository }}
          # PR number lives in different places per event:
          #   pull_request_target → github.event.pull_request.number
          #   issue_comment       → github.event.issue.number
          PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
          DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
          TEAM: qa
          TEAM_ID: '20'
          REVIEW_CHECK_DEBUG: '0'
          REVIEW_CHECK_STRICT: '0'
        run: bash .gitea/scripts/review-check.sh
