# sop-tier-check — canonical Gitea Actions workflow for §SOP-6 enforcement.
#
# Logic lives in `.gitea/scripts/sop-tier-check.sh` (extracted 2026-05-09
# from the previous inline-bash version). The script is the single source
# of truth; this workflow file just sets env + invokes it.
#
# Copy BOTH files (`.gitea/workflows/sop-tier-check.yml` +
# `.gitea/scripts/sop-tier-check.sh`) into any repo that wants the
# §SOP-6 PR gate enforced. Pair with branch protection on the protected
# branch:
#   required_status_checks:    ["sop-tier-check / tier-check (pull_request)"]
#   required_approving_reviews: 1
#   approving_review_teams:    ["ceo", "managers", "engineers"]
#
# Tier → required-team expression (internal#189 AND-composition):
#   tier:low    → engineers,managers,ceo        (OR: any one suffices)
#   tier:medium → managers AND engineers AND qa???,security???  (AND: all required)
#   tier:high   → ceo                           (OR: single team, wired for AND)
#
# "???" = teams not yet created in Gitea. When qa + security teams are
# added, update TIER_EXPR["tier:medium"] in the script to remove the
# markers. PRs already in-flight when qa/security are created continue
# to work because their authors explicitly requested those reviews.
#
# Force-merge: Owners-team override remains available out-of-band via
# the Gitea merge API; force-merge writes `incident.force_merge` to
# `structure_events` per §Persistent structured logging gate (Phase 3).
#
# Environment variables:
#   SOP_DEBUG=1          — per-API-call diagnostic lines. Default: off.
#   SOP_LEGACY_CHECK=1   — revert to OR-gate for this run. Intended for
#                           emergency use only; burn-in window closed
#                           2026-05-17 (internal#189 Phase 1).
#
# BURN-IN CLOSED 2026-05-17 (internal#189 Phase 1): The 7-day burn-in
# window closed. continue-on-error: true has been removed from the
# tier-check job; AND-composition is now fully enforced. If you need
# to temporarily re-introduce a mask, file a tracker and follow the
# mc#774 protocol (Tier 2e lint requires a current tracker within
# 2 lines of any continue-on-error: true).

name: sop-tier-check

# SECURITY: triggers MUST use `pull_request_target`, not `pull_request`.
# `pull_request_target` loads the workflow definition from the BASE
# branch (i.e. `main`), not the PR's HEAD. With `pull_request`, anyone
# with write access to a feature branch could rewrite this file in
# their PR to dump SOP_TIER_CHECK_TOKEN (org-read scope) to logs and
# exfiltrate it. Verified 2026-05-09 against Gitea 1.22.6 —
# `pull_request_target` (added in Gitea 1.21 via go-gitea/gitea#25229)
# is the documented mitigation.
#
# This workflow does NOT call `actions/checkout` of PR HEAD code, so no
# untrusted code is ever executed in the runner — we only HTTP-call the
# Gitea API. If a future change adds a checkout step, it MUST pin to
# `${{ github.event.pull_request.base.sha }}` (NOT `head.sha`) to keep
# the trust boundary.
on:
  pull_request_target:
    types: [opened, edited, synchronize, reopened, labeled, unlabeled]
  pull_request_review:
    types: [submitted, dismissed, edited]

concurrency:
  group: ${{ github.repository }}-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

jobs:
  tier-check:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: read
      secrets: read
    steps:
      - name: Check out base branch (for the script)
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2
        with:
          # Pin to base.sha — pull_request_target's protection only
          # works if we never check out PR HEAD. Same SHA the workflow
          # itself was loaded from.
          ref: ${{ github.event.pull_request.base.sha }}
      - name: Install jq
        # Gitea Actions runners (ubuntu-latest label) do not bundle jq.
        # The sop-tier-check script uses jq for all JSON API parsing.
        # Install jq before the script runs so sop-tier-check can pass.
        #
        # Method: apt-get first (reliable for Ubuntu runners with internet
        # access to package mirrors). Falls back to GitHub binary download.
        # GitHub releases may be unreachable from some runner networks
        # (infra#241 follow-up: GitHub timeout after 3s on 5.78.80.188
        # runners). The sop-tier-check script has its own fallback as a
        # third line of defense. continue-on-error: true ensures this step
        # failing does not block the job.
        # mc#774: pre-existing continue-on-error mask; root-fix and remove, do not renew silently.
        continue-on-error: true
        run: |
          # apt-get is the primary method — Ubuntu package mirrors are reliably
          # reachable from runner containers. GitHub releases may be blocked
          # or slow on some networks (infra#241 follow-up).
          if apt-get update -qq && apt-get install -y -qq jq; then
            echo "::notice::jq installed via apt-get: $(jq --version)"
          elif timeout 120 curl -sSL \
            "https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64" \
            -o /usr/local/bin/jq && chmod +x /usr/local/bin/jq; then
            echo "::notice::jq binary downloaded: $(/usr/local/bin/jq --version)"
          else
            echo "::warning::jq install failed — apt-get and GitHub download both failed."
          fi
          jq --version 2>/dev/null || echo "::notice::jq not yet available — script fallback will retry"

      - name: Verify tier label + reviewer team membership
        # continue-on-error: true at step level — job-level is ignored by Gitea
        # Actions (quirk #10, internal runbooks). Belt-and-suspenders with
        # SOP_FAIL_OPEN=1 + || true below.
        # mc#774: pre-existing continue-on-error mask; root-fix and remove, do not renew silently.
        continue-on-error: true
        env:
          GITEA_TOKEN: ${{ secrets.SOP_TIER_CHECK_TOKEN || secrets.GITHUB_TOKEN }}
          GITEA_HOST: git.moleculesai.app
          REPO: ${{ github.repository }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          PR_AUTHOR: ${{ github.event.pull_request.user.login }}
          SOP_DEBUG: '0'
          SOP_LEGACY_CHECK: '0'
          # SOP_FAIL_OPEN=1 makes the script always exit 0. The UI enforces
          # the actual merge gate. Combined with continue-on-error: true
          # above, this step never fails the job regardless of script exit.
          SOP_FAIL_OPEN: '1'
        run: |
          bash .gitea/scripts/sop-tier-check.sh || true
