# Warden security clearance.
#
# Grants or revokes a "security clearance" review on PRs based on the Warden
# diff-security-review verdict. Humans still review and merge; the clearance
# only satisfies the required-review gate in the branch ruleset for clean
# diffs.
#
# Why workflow_run: this file always executes from the default branch, so a
# PR cannot tamper with the clearance logic. The verdict step additionally
# refuses to self-clear any PR that touches review machinery
# (.github/, warden.toml, .warden/, .agents/skills/, .claude/skills/).
#
# Identity: a dedicated GitHub App (WARDEN_APP_ID / WARDEN_PRIVATE_KEY in the
# `warden-clearance` environment). The app never pushes commits, so
# require_last_push_approval always holds (clearance identity != pusher !=
# author). Until the app secrets are configured this workflow no-ops.

name: Warden Clearance

on:
  workflow_run:
    workflows: [Warden]
    types: [completed]

permissions:
  actions: read
  contents: read
  pull-requests: read

concurrency:
  group: warden-clearance-${{ github.event.workflow_run.head_branch }}
  cancel-in-progress: false

jobs:
  clearance:
    if: >-
      github.event.workflow_run.event == 'pull_request' &&
      github.event.workflow_run.conclusion == 'success' &&
      github.event.workflow_run.head_repository.full_name == github.repository
    runs-on: ubuntu-latest
    timeout-minutes: 10
    environment: warden-clearance
    steps:
      - name: Check clearance identity is configured
        id: identity
        env:
          APP_ID: ${{ secrets.WARDEN_APP_ID }}
        run: |
          if [ -z "$APP_ID" ]; then
            echo "WARDEN_APP_ID is not configured; clearance disabled."
            echo "configured=false" >> "$GITHUB_OUTPUT"
          else
            echo "configured=true" >> "$GITHUB_OUTPUT"
          fi

      - name: Download clearance summary
        if: steps.identity.outputs.configured == 'true'
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
        with:
          name: warden-summary
          run-id: ${{ github.event.workflow_run.id }}
          github-token: ${{ github.token }}

      - name: Evaluate verdict
        if: steps.identity.outputs.configured == 'true'
        id: verdict
        env:
          GH_TOKEN: ${{ github.token }}
          REPO: ${{ github.repository }}
          HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
        run: |
          set -euo pipefail

          verdict() { echo "verdict=$1" >> "$GITHUB_OUTPUT"; exit 0; }

          summary_sha="$(jq -r '.head_sha' warden-summary.json)"
          count="$(jq -r '.findings_count' warden-summary.json)"

          if [ "$summary_sha" != "$HEAD_SHA" ]; then
            echo "Summary SHA ($summary_sha) does not match analyzed SHA ($HEAD_SHA); skipping."
            verdict skip
          fi

          pr="$(gh api "repos/$REPO/commits/$HEAD_SHA/pulls" --jq '.[0].number // empty')"
          if [ -z "$pr" ]; then
            echo "No PR associated with $HEAD_SHA; skipping."
            verdict skip
          fi
          echo "pr=$pr" >> "$GITHUB_OUTPUT"

          current_head="$(gh api "repos/$REPO/pulls/$pr" --jq '.head.sha')"
          if [ "$current_head" != "$HEAD_SHA" ]; then
            echo "PR #$pr head moved to $current_head; skipping stale run."
            verdict skip
          fi

          # Never self-clear a PR that touches the review machinery.
          guarded="$(gh api --paginate "repos/$REPO/pulls/$pr/files" --jq '.[].filename' \
            | grep -E '^(\.github/|warden\.toml$|\.warden/|\.agents/skills/|\.claude/skills/)' || true)"
          if [ -n "$guarded" ]; then
            echo "PR #$pr touches review machinery; human review required:"
            echo "$guarded"
            verdict guarded
          fi

          if [ "$count" = "0" ]; then
            echo "No new security issues found for $HEAD_SHA."
            verdict clear
          else
            echo "$count finding(s) on $HEAD_SHA."
            verdict flagged
          fi

      - name: Mint clearance token
        if: steps.verdict.outputs.verdict == 'clear' || steps.verdict.outputs.verdict == 'flagged'
        id: app
        uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
        with:
          app-id: ${{ secrets.WARDEN_APP_ID }}
          private-key: ${{ secrets.WARDEN_PRIVATE_KEY }}

      - name: Grant clearance
        if: steps.verdict.outputs.verdict == 'clear'
        env:
          GH_TOKEN: ${{ steps.app.outputs.token }}
          REPO: ${{ github.repository }}
          PR: ${{ steps.verdict.outputs.pr }}
          HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
          RUN_URL: ${{ github.event.workflow_run.html_url }}
        run: |
          set -euo pipefail
          gh api "repos/$REPO/pulls/$PR/reviews" \
            -f event=APPROVE \
            -f commit_id="$HEAD_SHA" \
            -f body="**Warden security clearance: clear.** No new security issues found in this diff (\`$HEAD_SHA\`). Automated clearance satisfies the required-review gate only — a human still reviews and merges. [Analysis run]($RUN_URL)"

      - name: Revoke stale clearance
        if: steps.verdict.outputs.verdict == 'flagged'
        env:
          GH_TOKEN: ${{ steps.app.outputs.token }}
          REPO: ${{ github.repository }}
          PR: ${{ steps.verdict.outputs.pr }}
          APP_SLUG: ${{ steps.app.outputs.app-slug }}
        run: |
          set -euo pipefail
          rid="$(gh api "repos/$REPO/pulls/$PR/reviews" \
            --jq "[.[] | select(.user.login == \"${APP_SLUG}[bot]\" and .state == \"APPROVED\")] | last | .id // empty")"
          if [ -n "$rid" ]; then
            gh api -X PUT "repos/$REPO/pulls/$PR/reviews/$rid/dismissals" \
              -f message="Warden found new security findings on the latest diff; clearance revoked." \
              || echo "Could not dismiss review $rid (may already be dismissed)."
          else
            echo "No active clearance to revoke."
          fi
