diff --git a/.fullsend/config.yaml b/.fullsend/config.yaml new file mode 100644 index 000000000..c085ee069 --- /dev/null +++ b/.fullsend/config.yaml @@ -0,0 +1,33 @@ +# 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" +roles: + - fullsend + - triage + - coder + - fix + - review + - retro + - prioritize +agents: + - name: review + source: customized/harness/review.yaml + - name: retro + source: customized/harness/retro.yaml +allowed_remote_resources: + - https://raw.githubusercontent.com/fullsend-ai/fullsend/ + - https://raw.githubusercontent.com/fullsend-ai/agents/ +create_issues: + allow_targets: + repos: + - conforma/cli + - fullsend-ai/fullsend +status_notifications: + comment: + completion: on_failure + reaction: + start: enabled + completion: disabled diff --git a/.github/workflows/fullsend.yaml b/.github/workflows/fullsend.yaml index e838f9f31..11d46287e 100644 --- a/.github/workflows/fullsend.yaml +++ b/.github/workflows/fullsend.yaml @@ -1,80 +1,78 @@ -# Copyright The Conforma Contributors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -# lint-workflow-size: max-lines=280 -# fullsend shim workflow (workflow_call mode) -# Routes events to agent workflows in .fullsend via workflow_call. -# No secrets are needed in the enrolled repo — agents fetch scoped tokens -# from the centralized token mint using GitHub OIDC. +# 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 repo's context — no external +# config repo is needed. # # 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. # -# conforma is replaced by reconcile-repos.sh at deploy time. +# 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. # -# Routing: this shim forwards the raw event context to dispatch.yml, which -# determines the stage from event_type + event_action + payload fields. -# Adding a new stage requires only a case branch in dispatch.yml — zero -# changes to enrolled repos. +# Concurrency: per-role cancel-in-progress groups live in reusable-dispatch.yml +# stage jobs with -agent- suffix. Roles operate independently (#2452). name: fullsend -permissions: - actions: write - id-token: write - contents: read - pull-requests: read - on: issues: types: [opened, edited, labeled] issue_comment: types: [created] pull_request_target: - types: [opened, synchronize, ready_for_review, closed] + types: [opened, synchronize, ready_for_review, closed, labeled, unlabeled] pull_request_review: types: [submitted] +permissions: {} + jobs: dispatch: - concurrency: - group: fullsend-dispatch-${{ github.event.issue.number || github.event.pull_request.number }} - cancel-in-progress: false if: >- - github.event_name != 'issue_comment' - || github.event.comment.user.type != 'Bot' - uses: conforma/.fullsend/.github/workflows/dispatch.yml@cd011389fdff80dc8ef963f82f7f200901e62f5c # main + (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@main with: event_action: ${{ github.event.action }} + install_mode: per-repo + 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 }} + OTEL_EXPORTER_OTLP_TRACES_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_TRACES_HEADERS }} + OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }} 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' - && ( - github.event.comment.author_association == 'OWNER' - || github.event.comment.author_association == 'MEMBER' - || github.event.comment.author_association == 'COLLABORATOR' - || github.event.comment.author_association == 'CONTRIBUTOR' - || github.event.comment.user.login == github.event.issue.user.login - ) - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read issues: write @@ -85,7 +83,37 @@ jobs: 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 dispatch.yml; 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 + if role=$(gh api "repos/$REPO/collaborators/$COMMENT_USER_LOGIN/permission" \ + --jq '.role_name' 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" \ --force 2>/dev/null || true diff --git a/.github/workflows/prioritize.yml b/.github/workflows/prioritize.yml new file mode 100644 index 000000000..0330deb32 --- /dev/null +++ b/.github/workflows/prioritize.yml @@ -0,0 +1,51 @@ +# 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: + required: true + type: string + source_repo: + required: true + type: string + event_payload: + 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 }} + cancel-in-progress: true + +jobs: + prioritize: + uses: fullsend-ai/fullsend/.github/workflows/reusable-prioritize.yml@main + 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 }} + install_mode: per-repo + 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 }} + OTEL_EXPORTER_OTLP_TRACES_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_TRACES_HEADERS }} + OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}