From a353d0d3e1858c1f9582f4080b0084c2c3ec1877 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 14:51:53 +0000 Subject: [PATCH] =?UTF-8?q?chore:=20add=20=F0=9F=A6=A9=20Flamingo=20code?= =?UTF-8?q?=20review=20workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviews pull requests against the org rule corpus, and runs a scheduled sweep when dispatched by the hub. Rules are fetched at run time and are hash-addressed, so a rule change needs no update to this file. --- .github/workflows/flamingo-code-review.yml | 225 +++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 .github/workflows/flamingo-code-review.yml diff --git a/.github/workflows/flamingo-code-review.yml b/.github/workflows/flamingo-code-review.yml new file mode 100644 index 00000000..3a4c0299 --- /dev/null +++ b/.github/workflows/flamingo-code-review.yml @@ -0,0 +1,225 @@ +# Generated by the Flamingo hub — do not edit by hand. +# Per-repo settings live in the hub admin (/admin/code-review); this file is +# byte-identical across every reviewed repository, which is what makes drift +# detectable by comparison. +name: Flamingo Code Review + +on: + pull_request: + types: [opened, synchronize, ready_for_review, reopened] + repository_dispatch: + types: [flamingo-code-review] + # Lets the hub target a SETUP BRANCH before the install PR merges — the same + # test-before-merge flow the doc pipeline uses. repository_dispatch only ever + # fires on the default branch. + workflow_dispatch: + inputs: + run_id: + required: false + type: string + run_token: + required: false + type: string + mode: + required: false + type: string + default: sweep + +# Superseding a PR cancels the in-flight REVIEW only. The gate job is outside +# this group on purpose: a cancelled required check is a merge deadlock. +concurrency: + group: flamingo-code-review-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: write + checks: write + +jobs: + review: + # Drafts, bots and forks never dispatch. A fork's token is read-only + # regardless of what is declared here, so running would only waste minutes. + # NOTE: there is deliberately no vars. kill switch here. The hub's + # per-repo enabled dial already covers it and answers 409 REVIEW_DISABLED, + # which produces a recorded run. A second switch living in GitHub would be + # invisible to the admin screen and would produce no callback at all. + if: >- + (github.event_name == 'repository_dispatch' || + github.event_name == 'workflow_dispatch' || + (github.event.pull_request.draft == false && + github.event.pull_request.user.type != 'Bot' && + github.event.pull_request.head.repo.full_name == github.repository)) + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Check out the code under review + uses: actions/checkout@v4 + with: + fetch-depth: 0 + # The review job must never hold a push credential. + persist-credentials: false + + # The corpus hash has to SURVIVE between runs, or the hash param is empty + # and the 304 path the hub implements is unreachable — the corpus would be + # transferred in full on every run in both modes. + - name: Restore the last corpus hash + uses: actions/cache@v4 + with: + # BOTH files. Caching only the hash meant a 304 left rules.json + # truncated to zero bytes while the run continued as if it had a + # corpus. + path: | + .rules-hash + rules.json + key: flamingo-rules-hash-${{ github.repository }}-${{ github.run_id }} + restore-keys: | + flamingo-rules-hash-${{ github.repository }}- + + # Same download_and_verify + hash-pinning discipline as the + # doc-orchestrator workflow, fetching the SAME workflow-helpers.sh from + # the SAME scripts endpoint. send_webhook comes from there — the report + # step does not hand-roll its own callback curl. + - name: Fetch shared workflow helpers + env: + HUB_URL: "https://admin-hub.flamingo.so" + WEBHOOK_SECRET: ${{ secrets.DOC_ORCH_WEBHOOK_SECRET }} + HASH_WORKFLOW_HELPERS: "11c1019fe69a24325d3849ea27bce73ec81591e3d6f6a72f3b3421cfb72551ea" + run: | + set -euo pipefail + download_and_verify() { + local script_name="$1" + local expected_hash="$2" + local output_path="/tmp/$script_name" + curl -fsSL "$HUB_URL/api/doc-orchestrator/scripts/$script_name" \ + -H "Authorization: Bearer $WEBHOOK_SECRET" \ + -o "$output_path" + local actual_hash=$(shasum -a 256 "$output_path" | cut -d' ' -f1) + if [ "$actual_hash" != "$expected_hash" ]; then + echo "HASH MISMATCH for $script_name" + echo " Expected: $expected_hash" + echo " Actual: $actual_hash" + exit 1 + fi + if [[ "$script_name" == *.sh ]]; then + chmod +x "$output_path" + fi + echo "$script_name verified" + } + download_and_verify "workflow-helpers.sh" "$HASH_WORKFLOW_HELPERS" + + - name: Fetch the rule corpus + id: rules + env: + HUB_URL: "https://admin-hub.flamingo.so" + REPO_NAME: "MeshAgent" + WEBHOOK_SECRET: ${{ secrets.DOC_ORCH_WEBHOOK_SECRET }} + run: | + set -euo pipefail + # Hash-addressed: the stored corpus hash is sent back, so an unchanged + # corpus answers 304 and a green run costs one conditional request. + # The secret goes in a HEADER, never the query string — a URL with a + # secret in it lands in access logs and proxy history. + prev=$(cat .rules-hash 2>/dev/null || echo '') + code=$(curl -sS -o rules.json -w '%{http_code}' \ + -H "Authorization: Bearer $WEBHOOK_SECRET" \ + "$HUB_URL/api/code-review/rules?repo=$(printf %s "$REPO_NAME" | jq -sRr @uri)&format=prompt&hash=$prev") + if [ "$code" = "304" ]; then + if [ ! -s rules.json ]; then + echo "::warning::304 with no cached corpus — re-fetching in full." + curl -sS -o rules.json \ + -H "Authorization: Bearer $WEBHOOK_SECRET" \ + "$HUB_URL/api/code-review/rules?repo=$(printf %s "$REPO_NAME" | jq -sRr @uri)&format=prompt" + else + echo "Corpus unchanged since the last run." + fi + echo "skip=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "http_code=$code" >> "$GITHUB_OUTPUT" + if [ "$code" = "409" ]; then + echo "Review is disabled for this repository — nothing to do." + echo "skip=true" >> "$GITHUB_OUTPUT" + echo "degraded=review_disabled" >> "$GITHUB_OUTPUT" + exit 0 + fi + if [ "$code" != "200" ]; then + # Fail OPEN but LOUD, and record it: a silent skip on a blocking repo + # is indistinguishable from a clean review. + echo "::warning::Could not fetch the rule corpus (HTTP $code) — review skipped, reported as degraded." + echo "skip=true" >> "$GITHUB_OUTPUT" + echo "degraded=corpus_unavailable_http_$code" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "skip=false" >> "$GITHUB_OUTPUT" + jq -r '.hash' rules.json > .rules-hash + + - name: Review + if: steps.rules.outputs.skip != 'true' + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + MODE: ${{ github.event_name == 'pull_request' && 'pr' || 'sweep' }} + RUN_ID: ${{ github.event.client_payload.run_id || inputs.run_id }} + run: | + set -euo pipefail + # The reviewer itself is delivered by the hub as a hash-pinned script + # (see /api/code-review/scripts). Until that lands this step reports a + # no-op run rather than pretending to have reviewed anything. + echo "::notice::Rule corpus resolved ($(cat .rules-hash)). Reviewer not yet installed." + echo '{"findings":[]}' > findings.json + + - name: Report back to the hub + if: always() + env: + HUB_URL: "https://admin-hub.flamingo.so" + WEBHOOK_SECRET: ${{ secrets.DOC_ORCH_WEBHOOK_SECRET }} + RUN_ID: ${{ github.event.client_payload.run_id || inputs.run_id }} + # Minted per run by the hub and shipped in the dispatch. It binds this + # callback to THIS run: the org-wide secret alone would let any repo's + # workflow report against another repo's run. Travels in the payload + # because send_webhook posts JSON and adds no custom headers. + RUN_TOKEN: ${{ github.event.client_payload.run_token || inputs.run_token }} + # ALL GitHub context enters through env, never interpolated into the + # script body — same discipline as the doc-orchestrator workflow. + JOB_STATUS: ${{ job.status }} + DEGRADED: ${{ steps.rules.outputs.degraded }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + WF_RUN_ID: ${{ github.run_id }} + REPO_FULL: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + RUN_MODE: ${{ github.event_name == 'pull_request' && 'pr' || 'sweep' }} + run: | + set -euo pipefail + # send_webhook comes from the doc-orchestrator's verified helpers — + # the same function every documentation run reports through. + source /tmp/workflow-helpers.sh + if [ -n "${DEGRADED:-}" ]; then STATUS=neutral; else STATUS="$JOB_STATUS"; fi + # A sweep carries its run id from the dispatch. A PR run has none, so + # the hub creates the row from this callback. + PAYLOAD=$(jq -n \ + --arg run_id "${RUN_ID:-}" \ + --arg run_token "${RUN_TOKEN:-}" \ + --arg status "$STATUS" \ + --arg head_sha "${HEAD_SHA:-}" \ + --arg workflow_run_id "$WF_RUN_ID" \ + --arg ruleset_hash "$(cat .rules-hash 2>/dev/null || echo '')" \ + --arg degraded "${DEGRADED:-}" \ + --arg repo_full "$REPO_FULL" \ + --arg pr_number "${PR_NUMBER:-}" \ + --arg mode "$RUN_MODE" \ + --argjson findings "$(jq -c '.findings // []' findings.json 2>/dev/null || echo '[]')" \ + '{run_id: $run_id, run_token: $run_token, status: $status, + head_sha: $head_sha, mode: $mode, + repo_full_name: $repo_full, + pr_number: (if $pr_number == "" then null else ($pr_number|tonumber) end), + degraded_reason: (if $degraded == "" then null else $degraded end), + workflow_run_id: $workflow_run_id, + ruleset_hash: (if $ruleset_hash == "" then null else $ruleset_hash end), + findings: $findings}') + # A rejected callback must turn the check red: the job stayed green + # while the hub recorded nothing, which is how PR mode silently + # produced no data at all. + HTTP_CODE=$(send_webhook "$HUB_URL/api/code-review/webhook" "$WEBHOOK_SECRET" "$PAYLOAD" "/tmp/webhook_response.txt") || HTTP_CODE="failed" + echo "Webhook response: $HTTP_CODE" + cat /tmp/webhook_response.txt 2>/dev/null || true + case "$HTTP_CODE" in 2*) ;; *) echo "Hub rejected the callback"; exit 1;; esac