From 9426f20b4af8e4449d49b5ecea9b48de3b4997dd Mon Sep 17 00:00:00 2001 From: Erin Bournival Date: Wed, 28 Oct 2026 11:26:27 -0400 Subject: [PATCH] chore: initialize fullsend per-repo installation --- .fullsend/config.yaml | 23 ++++++ .github/workflows/fullsend.yaml | 138 +++++++++++++++++++++++++++++++ .github/workflows/prioritize.yml | 55 ++++++++++++ 3 files changed, 216 insertions(+) create mode 100644 .fullsend/config.yaml create mode 100644 .github/workflows/fullsend.yaml create mode 100644 .github/workflows/prioritize.yml diff --git a/.fullsend/config.yaml b/.fullsend/config.yaml new file mode 100644 index 000000000..b6680d03d --- /dev/null +++ b/.fullsend/config.yaml @@ -0,0 +1,23 @@ +# fullsend per-repo configuration +# https://github.com/fullsend-ai/fullsend +# +# This file configures fullsend for per-repo installation mode. +# See https://fullsend.sh/docs/guides/infrastructure/layered-config-reference +version: "1" +runtime: pi +roles: + - triage + - coder + - review + - fix +allowed_remote_resources: + - https://raw.githubusercontent.com/fullsend-ai/fullsend/ + - https://raw.githubusercontent.com/fullsend-ai/agents/ +create_issues: + allow_targets: + repos: + - fullsend-ai/fullsend + - fullsend-ai/agents +inference: + project: firefly-868139 + wif_provider: projects/275116376890/locations/global/workloadIdentityPools/uie-github-fullsend-inference/providers/gh-repo-975968014 diff --git a/.github/workflows/fullsend.yaml b/.github/workflows/fullsend.yaml new file mode 100644 index 000000000..eefcaf447 --- /dev/null +++ b/.github/workflows/fullsend.yaml @@ -0,0 +1,138 @@ +# This file is managed by fullsend. Do not edit it directly. +# Upstream: https://github.com/fullsend-ai/fullsend/blob/main/internal/scaffold/fullsend-repo/.github/workflows/fullsend.yaml +--- +# fullsend shim workflow (per-repo installation mode) +# Routes events to agent workflows via reusable-dispatch.yml. +# All agent execution happens in this repository's context. +# +# Security: pull_request_target runs the BASE branch version of this workflow, +# preventing PRs from modifying it to exfiltrate credentials. +# This shim never checks out PR code, so it is not vulnerable to "pwn request" +# attacks. +# +# Routing: this shim forwards the raw event context to reusable-dispatch.yml, +# which determines the stage and runs the agent inline (ADR 62). +# Adding a new stage requires only a job in reusable-dispatch.yml — zero changes to this repo. +# +# Concurrency: per-role cancel-in-progress groups live in reusable-dispatch.yml +# stage jobs with -agent- suffix. Roles operate independently (#2452). +name: fullsend + +on: + issues: + types: [opened, edited, labeled] + issue_comment: + types: [created] + pull_request_target: + types: [opened, synchronize, ready_for_review, closed, labeled, unlabeled] + pull_request_review: + types: [submitted] + +permissions: {} + +jobs: + dispatch: + if: >- + (github.event_name != 'pull_request_target' && github.event_name != 'pull_request_review' + || github.event.pull_request.head.ref != 'fullsend/scaffold-install') + && (github.event_name != 'issue_comment' + || (startsWith(github.event.comment.body, '/fs-') + && github.event.comment.user.type != 'Bot')) + permissions: + actions: write + id-token: write + contents: write + issues: write + packages: read + pull-requests: write + uses: fullsend-ai/fullsend/.github/workflows/reusable-dispatch.yml@f3b9d9869483093802aeb000f1d931880c9a48b5 # v0.51.0 + with: + event_action: ${{ github.event.action }} + mint_url: ${{ vars.FULLSEND_MINT_URL }} + gcp_region: ${{ vars.FULLSEND_GCP_REGION }} + project_number: ${{ vars.FULLSEND_PROJECT_NUMBER }} + runner_image: ubuntu-24.04 + secrets: + FULLSEND_GCP_WIF_PROVIDER: ${{ secrets.FULLSEND_GCP_WIF_PROVIDER }} + FULLSEND_GCP_PROJECT_ID: ${{ secrets.FULLSEND_GCP_PROJECT_ID }} + FULLSEND_OPENAI_API_KEY: ${{ secrets.FULLSEND_OPENAI_API_KEY }} + FULLSEND_INFERENCE_GATEWAY_API_KEY: ${{ secrets.FULLSEND_INFERENCE_GATEWAY_API_KEY }} + OTEL_EXPORTER_OTLP_TRACES_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_TRACES_HEADERS }} + OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }} + FULLSEND_GITHUB_RUNNER_SECRETS: ${{ secrets.FULLSEND_GITHUB_RUNNER_SECRETS }} + + stop-fix: + # Job-level if: is intentionally coarse — it only screens for the + # /fs-fix-stop command on a PR from a non-bot. The authoritative + # authorization decision (collaborator permission API + PR-author escape + # hatch) is made in the step below, so a maintainer whose author_association + # is not MEMBER (e.g. private org membership) is not filtered out (ADR 0054). + if: >- + github.event_name == 'issue_comment' + && github.event.issue.pull_request + && github.event.comment.user.type != 'Bot' + && github.event.comment.body == '/fs-fix-stop' + runs-on: ubuntu-24.04 + permissions: + contents: read + issues: write + pull-requests: write + steps: + - name: Add fullsend-no-fix label and notify + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.issue.number }} + REPO: ${{ github.repository }} + COMMENT_USER_LOGIN: ${{ github.event.comment.user.login }} + ISSUE_USER_LOGIN: ${{ github.event.issue.user.login }} + run: | + set -euo pipefail + # ADR 0054: authorize via the collaborator permission API + # (admin|maintain|write), not author_association — the latter grants + # contributor status to anyone with a single merged PR (issue #5421). + # Mirrors has_repo_permission() in reusable-dispatch.yml, including the + # custom-role --jq resolution; keep the two in sync. + # The PR author may always stop the fix agent on their own PR. + authorized=false + if [[ -n "$COMMENT_USER_LOGIN" && "$COMMENT_USER_LOGIN" == "$ISSUE_USER_LOGIN" ]]; then + authorized=true + else + if api_err=$(mktemp); then + # $p is a jq variable; the single quotes are intentional. + # shellcheck disable=SC2016 + if role=$(gh api "repos/$REPO/collaborators/$COMMENT_USER_LOGIN/permission" \ + --jq '.user.permissions as $p + | if (.role_name | type) != "string" or .role_name == "" then "none" + elif (.role_name | IN("admin", "maintain", "write", "triage", "read")) then .role_name + elif $p == null then + if (.permission | IN("admin", "write", "read")) then .permission else "none" end + elif ($p | type) == "object" then + if $p.admin == true then "admin" + elif $p.maintain == true then "maintain" + elif $p.push == true then "write" + elif $p.triage == true then "triage" + elif $p.pull == true then "read" + else "none" end + else "none" end' 2>"$api_err"); then + case "$role" in + admin|maintain|write) authorized=true ;; + esac + else + echo "::warning::Permission API call failed for $COMMENT_USER_LOGIN: $(cat "$api_err")" + fi + rm -f "$api_err" + else + echo "::warning::Failed to create temp file for permission check of $COMMENT_USER_LOGIN" + fi + fi + if [[ "$authorized" != "true" ]]; then + echo "::notice::User $COMMENT_USER_LOGIN is not authorized to stop the fix agent (requires write access or PR authorship)" + exit 0 + fi + gh label create "fullsend-no-fix" --repo "$REPO" \ + --description "Skip bot-triggered fix agent runs" --color "FBCA04" \ + 2>/dev/null || true + gh pr edit "$PR_NUMBER" --repo "$REPO" \ + --add-label "fullsend-no-fix" + gh pr comment "$PR_NUMBER" --repo "$REPO" \ + --body "Fix agent disabled for this PR. Remove the \`fullsend-no-fix\` label or use \`/fs-fix\` to re-engage." diff --git a/.github/workflows/prioritize.yml b/.github/workflows/prioritize.yml new file mode 100644 index 000000000..7c5003dc7 --- /dev/null +++ b/.github/workflows/prioritize.yml @@ -0,0 +1,55 @@ +# This file is managed by fullsend. Do not edit it directly. +# Upstream: https://github.com/fullsend-ai/fullsend/blob/main/internal/scaffold/fullsend-repo/.github/workflows/prioritize.yml +--- +# fullsend-stage: prioritize +name: Prioritize + +permissions: + actions: write + contents: read + id-token: write + issues: write + +on: + workflow_dispatch: + inputs: + event_type: + description: Event type that triggered prioritization + required: true + type: string + source_repo: + description: Repository the event came from (owner/repo) + required: true + type: string + event_payload: + description: JSON-encoded payload of the triggering event + required: true + type: string + project_number: + description: GitHub Projects V2 project number for RICE scoring + required: false + type: string + +concurrency: + group: fullsend-prioritize-${{ inputs.source_repo }}-${{ fromJSON(inputs.event_payload).issue.number || fromJSON(inputs.event_payload).pull_request.number }} + cancel-in-progress: true + +jobs: + prioritize: + uses: fullsend-ai/fullsend/.github/workflows/reusable-prioritize.yml@f3b9d9869483093802aeb000f1d931880c9a48b5 # v0.51.0 + with: + event_type: ${{ inputs.event_type }} + source_repo: ${{ inputs.source_repo }} + event_payload: ${{ inputs.event_payload }} + mint_url: ${{ vars.FULLSEND_MINT_URL }} + gcp_region: ${{ vars.FULLSEND_GCP_REGION }} + project_number: ${{ inputs.project_number || vars.FULLSEND_PROJECT_NUMBER }} + runner_image: ubuntu-24.04 + secrets: + FULLSEND_GCP_WIF_PROVIDER: ${{ secrets.FULLSEND_GCP_WIF_PROVIDER }} + FULLSEND_GCP_PROJECT_ID: ${{ secrets.FULLSEND_GCP_PROJECT_ID }} + FULLSEND_OPENAI_API_KEY: ${{ secrets.FULLSEND_OPENAI_API_KEY }} + FULLSEND_INFERENCE_GATEWAY_API_KEY: ${{ secrets.FULLSEND_INFERENCE_GATEWAY_API_KEY }} + OTEL_EXPORTER_OTLP_TRACES_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_TRACES_HEADERS }} + OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }} + FULLSEND_GITHUB_RUNNER_SECRETS: ${{ secrets.FULLSEND_GITHUB_RUNNER_SECRETS }}