Skip to content

feat(dispatch): allow triage role for observation stages - #5244

Merged
ascerra merged 9 commits into
mainfrom
feat/5223-triage-role-auth
Jul 20, 2026
Merged

feat(dispatch): allow triage role for observation stages#5244
ascerra merged 9 commits into
mainfrom
feat/5223-triage-role-auth

Conversation

@ascerra

@ascerra ascerra commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Parameterize dispatch auth with has_repo_permission(user, min) so GitHub Triage role can trigger observation stages (/fs-triage, auto-triage, /fs-review, auto-review) without gaining mutation access (/fs-code, /fs-fix stay write+)
  • Apply the same routing change to both reusable-dispatch.yml and scaffold dispatch.yml
  • Gate mutation label path ready-to-code to write+ labelers (bots still allowed for agent handoff)
  • Document intentional ungated pull_request_target.closed → retro
  • Update agent/user docs and annotate ADR 0054; add scaffold tests for the per-stage boundary

Closes #5223

Related: #5154 (per-repo configurable min permission left open — this PR hardcodes the triage floor for observation stages)

Test plan

  • go test ./internal/scaffold/ -run 'TestDispatchPerStageAuthorization|TestDispatchWorkflowContent'
  • make lint (after bootstrap pinact/lychee)
  • Confirm a triage-role collaborator can /fs-triage and open issues/PRs that auto-dispatch
  • Confirm a triage-role collaborator cannot /fs-code, /fs-fix, or trigger code via ready-to-code
  • Confirm write+ users and bot handoffs are unchanged

Extend has_repo_permission so triage-role collaborators can trigger
triage and review dispatch without gaining code/fix (write+) access.

Closes #5223

Signed-off-by: Adam Scerra <ascerra@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 17, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:25 PM UTC · Completed 2:39 PM UTC
Commit: 4b411a6 · View workflow run →

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Dispatch: allow GitHub triage role for observation stages

✨ Enhancement 📝 Documentation 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Parameterize dispatch authorization to allow GitHub **triage** for triage/review stages.
• Keep mutation stages (code/fix and write-gated commands) restricted to write+.
• Update scaffold tests and docs/ADR to codify per-stage authorization boundaries.
Diagram

graph TD
  A["Dispatch workflows"] --> B["has_repo_permission"] --> C["GitHub collab API"]
  B --> D{{"Min level?"}} --> E["Obs stages"]
  D --> F["Mut stages"]
  A --> G["Scaffold tests"]
  A --> H["Docs & ADR"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Separate helpers (has_triage_permission + has_write_permission)
  • ➕ Clearer call sites (no stringly-typed min parameter).
  • ➕ Avoids accidental passing of unsupported min values.
  • ➖ More duplicated logic (role parsing + API call).
  • ➖ Harder to extend beyond two levels without combinatorial helpers.
2. Stage→min-permission mapping table
  • ➕ Centralizes policy (all stage requirements in one place).
  • ➕ Easier to audit and evolve as stages are added.
  • ➖ Refactor larger than needed for this change.
  • ➖ May reduce readability if implemented in bash with complex parsing.

Recommendation: The chosen parameterized helper (has_repo_permission(user, min)) is a good minimal extension that preserves the existing security invariant (mutation stages stay write+). If additional permission tiers or stages are expected soon, consider a centralized stage→min mapping to keep authorization policy auditable.

Files changed (8) +116 / -37

Enhancement (2) +45 / -29
reusable-dispatch.ymlAllow triage-min authorization for triage/review dispatch paths +22/-14

Allow triage-min authorization for triage/review dispatch paths

• Replaces the write-only authorization helper with a parameterized 'has_repo_permission(user, min)' check. Updates slash-command and event-actor authorization so observation stages (triage/review) accept 'min=triage' while mutation stages remain write-gated.

.github/workflows/reusable-dispatch.yml

dispatch.ymlMirror triage-min authorization behavior in scaffold dispatch workflow +23/-15

Mirror triage-min authorization behavior in scaffold dispatch workflow

• Applies the same 'has_repo_permission(min)' approach as the reusable workflow so scaffolded repos enforce identical per-stage boundaries. Also adjusts the workflow size lint limit to accommodate the added logic.

internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml

Tests (2) +50 / -0
scaffold_test.goStrengthen scaffold workflow content assertions for per-stage auth +5/-0

Strengthen scaffold workflow content assertions for per-stage auth

• Extends existing scaffold workflow content tests to assert presence of 'has_repo_permission' and confirm observation stages use 'is_authorized triage' while mutation stages remain on default write-gated checks.

internal/scaffold/scaffold_test.go

workflow_call_alignment_test.goAdd per-stage authorization boundary test for both workflows +45/-0

Add per-stage authorization boundary test for both workflows

• Introduces 'TestDispatchPerStageAuthorization' to validate that both the reusable and scaffold dispatch workflows accept triage-role users for triage/review triggers but not for code/fix triggers, including auto-triage/auto-review event paths.

internal/scaffold/workflow_call_alignment_test.go

Documentation (4) +21 / -8
0054-require-authorization-on-all-agent-dispatch-paths.mdAnnotate ADR with triage-role exception for observation stages +7/-0

Annotate ADR with triage-role exception for observation stages

• Adds a dated note documenting that triage/review stages now accept the GitHub 'triage' role via 'has_repo_permission(min=triage)'. Reaffirms mutation stages remain 'min=write' to follow the ADR’s extension guidance.

docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md

review.mdUpdate /fs-review permission requirement to triage+ +4/-2

Update /fs-review permission requirement to triage+

• Adjusts the documented authorization threshold for '/fs-review' and its auto-review triggers to triage-level permission or higher, while explicitly noting mutation stages still require write+.

docs/agents/review.md

triage.mdUpdate /fs-triage permission requirement to triage+ +4/-2

Update /fs-triage permission requirement to triage+

• Updates documentation so '/fs-triage' and its automatic triggers require triage-level permission or higher, and clarifies that mutation stages (e.g., '/fs-code') remain write-gated.

docs/agents/triage.md

bugfix-workflow.mdDocument stage-dependent slash-command authorization +6/-4

Document stage-dependent slash-command authorization

• Replaces the blanket “all commands require write” statement with stage-dependent rules: triage/review accept triage+, while code/fix/retro/fix-stop remain write+. Keeps the note that bot-to-bot handoffs are label-triggered and unaffected.

docs/guides/user/bugfix-workflow.md

@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@qodo-code-review

qodo-code-review Bot commented Jul 17, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 61 rules

Grey Divider


Action required

1. Triage can trigger fix ✓ Resolved 🐞 Bug ⛨ Security
Description
Review is now authorized at min=triage, and the review workflow posts formal PR reviews that can be
REQUEST_CHANGES. The dispatcher then routes review-bot changes_requested events to STAGE="fix" based
on label/bot conditions without any write-level permission check, allowing triage to indirectly
reach a mutation-stage dispatch path.
Code

.github/workflows/reusable-dispatch.yml[R207-212]

                /fs-review)
                  if [[ "${ISSUE_IS_PR}" == "true" ]]; then
-                    if [[ "${COMMENT_USER_TYPE}" != "Bot" ]] && is_authorized; then
+                    if [[ "${COMMENT_USER_TYPE}" != "Bot" ]] && is_authorized triage; then
                      STAGE="review"
                    fi
                  fi
Relevance

⭐⭐⭐ High

Team enforces strict dispatch auth boundaries (ADR0054 in PR fullsend-ai/fullsend#1688) and hardened review→fix routing
before (#906, fullsend-ai/fullsend#337).

PR-#1688
PR-#906
PR-#337

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The diff change authorizes /fs-review at triage level, while the existing dispatcher logic can
auto-route a review-bot changes_requested review into the fix stage without checking write
permissions; the review workflow posts formal PR reviews via fullsend post-review, which maps to
GitHub’s REQUEST_CHANGES event and triggers the pull_request_review routing.

.github/workflows/reusable-dispatch.yml[207-213]
.github/workflows/reusable-dispatch.yml[294-309]
internal/scaffold/fullsend-repo/scripts/post-review.sh[317-322]
internal/cli/postreview.go[176-186]
internal/cli/postreview.go[366-372]
internal/forge/github/github.go[2639-2693]
internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml[226-244]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Triage-level authorization was extended to the review stage, but the `pull_request_review` (changes_requested) routing path can still dispatch `STAGE="fix"` without a write-level authorization check. This creates a transitive path from triage-authorized review to mutation-stage fix.

### Issue Context
- Review runs can create `pull_request_review` events via `fullsend post-review` (formal GitHub PR reviews).
- The dispatcher’s `pull_request_review` path currently gates fix only on "review bot" + label/bot-PR checks, not on repo write permissions.

### Fix Focus Areas
- Add a write-level permission check in the `pull_request_review` → `fix` branch (for both reusable + scaffold dispatch workflows), e.g. require `has_repo_permission "${PR_USER_LOGIN}" write` before setting `STAGE="fix"` for human PRs.
- Update/extend scaffold tests to assert the write-level gate exists on the `pull_request_review` fix routing path.

- .github/workflows/reusable-dispatch.yml[294-309]
- internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml[226-244]
- internal/scaffold/workflow_call_alignment_test.go[437-479]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. ADR consequence now incorrect ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
ADR 0054 now notes that observation stages accept triage permission, but the Consequences section
still says all dispatch paths require write-level permission. This contradiction can mislead future
changes/reviews about the actual authorization boundary.
Code

docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md[R189-191]

## Consequences

- All dispatch paths require write-level repository permission,
Relevance

⭐⭐⭐ High

Docs contradictions are typically fixed; similar “clarify contradiction/accuracy” ADR notes were
accepted (e.g., PR fullsend-ai/fullsend#716, fullsend-ai/fullsend#1688).

PR-#716
PR-#1688

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The added note states observation stages accept triage, but immediately after, the Consequences
section still claims all dispatch paths require write-level permission.

docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md[182-197]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The ADR’s new note explicitly allows triage-level permission for observation stages, but the Consequences bullet still claims all dispatch paths require write permission.

### Issue Context
This ADR is used as a design/security reference; contradictory statements increase the chance of future authorization regressions.

### Fix Focus Areas
- Update the Consequences bullet(s) to reflect stage-dependent authorization (observation stages can be triage+, mutation stages remain write+).

- docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md[182-200]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Raised workflow size limit ✓ Resolved 📘 Rule violation ▣ Testability
Description
The PR increases lint-workflow-size max-lines in
internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml, which weakens an existing lint
boundary via a comment-only change. This can hide future workflow growth and contradicts the
requirement to avoid weakening linters or making comment-only edits outside the issue scope.
Code

internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml[2]

+# lint-workflow-size: max-lines=490
Relevance

⭐⭐ Medium

They’ve accepted raising lint-workflow-size caps when needed (#791); no clear precedent rejecting
max-lines bumps.

PR-#791

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 1062076 disallows weakening linters to make failures pass, and rule 1062072 disallows
comment-only edits in isolation; the PR changes only the lint-workflow-size limit comment to a
higher value in dispatch.yml.

Rule 1062076: Do not weaken tests or linters to make failures pass
Rule 1062072: Do not add or modify comments outside code lines changed for the issue
internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml[1-3]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`dispatch.yml` raises the `lint-workflow-size: max-lines` threshold (475 → 490). This weakens an existing lint constraint and is done as a comment-only edit.

## Issue Context
The PR adds routing/auth logic, but increasing the allowed workflow size reduces enforcement against workflow bloat and may have been done only to make lint pass.

## Fix Focus Areas
- internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml[1-3]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml Outdated
Comment thread .github/workflows/reusable-dispatch.yml
Comment thread docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This PR modifies a file under the .github/ protected path. The PR links to issue dispatch: relax authorization gate to Triage role for triage stage and auto-review #5223 and the description explains the rationale for modifying dispatch authorization logic. Human approval is always required for changes to governance and infrastructure files, regardless of context.

  • [authorization/privilege-relaxation] .github/workflows/reusable-dispatch.yml:153 — The has_repo_permission function accepts a min parameter controlling the authorization threshold. When min is triage, users with only the GitHub Triage role can trigger observation stages (triage, review). The implementation is correctly structured: admin|maintain|write always pass, triage passes only when min=="triage", unknown min values default to write+ (fail-closed). All call sites pass literal string values, never user-controlled input. This is an intentional privilege relaxation authorized by ADR 0054 and issue dispatch: relax authorization gate to Triage role for triage stage and auto-review #5223. Verify that the organization's triage-role membership is intentionally scoped.

Low

  • [consumer-completeness] docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md:179 — The Decision section references "extending the has_write_permission function's allowed permission list" as the future extension path. The implementation uses has_repo_permission(user, min), with has_write_permission preserved as a backward-compatible wrapper. The ADR's guidance describes a design principle ("extend, don't bypass") that the PR correctly follows.

  • [authorization/ungated-path] .github/workflows/reusable-dispatch.yml:281 — The pull_request_target.closed → retro path remains intentionally ungated (pre-existing). The PR adds a comment documenting this as intentional per ADR 0054. The retro stage operates with read-only permissions (contents: read), limiting blast radius.

  • [stale-doc] docs/ADRs/0059-public-mint-mode-with-wildcard-allowlists.md:108 — States "agent runs still require write permission at the dispatch layer" which is now incomplete — observation stages accept triage permission. Since ADR 0059 is an accepted ADR, a Note annotation would be the appropriate mechanism.

  • [stale-doc] docs/ADRs/0068-public-community-mint-architecture.md:77 — States "mint openness does not bypass write checks at dispatch" which is now incomplete since observation stages use triage-level checks. A Note annotation would be appropriate.

Previous run

Review

Findings

Medium

  • [adr-immutability-violation] docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md:185 — The Consequences section of this Accepted ADR is substantively rewritten. The original text states "All dispatch paths require write-level repository permission" and is changed to "All dispatch paths require authorization — write-level for mutation stages, triage-level or higher for observation stages." The Decision-section Note annotation is a permitted minor annotation, but rewriting a Consequences bullet to change the factual claim crosses into substantive modification. Per AGENTS.md: "Do not substantially rewrite its Context, Decision, or Consequences sections."
    Remediation: Either (a) revert the Consequences bullet to its original wording and let the Decision-section note carry the updated semantics, or (b) write a new ADR that supersedes ADR 0054.

  • [protected-path] .github/workflows/reusable-dispatch.yml — This PR modifies a file under the .github/ protected path. The PR links to issue dispatch: relax authorization gate to Triage role for triage stage and auto-review #5223 and the description explains the rationale for modifying dispatch authorization logic. Human approval is always required for changes to governance and infrastructure files, regardless of context.

Low

  • [consumer-completeness] docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md:179 — The Decision section still references "extending the has_write_permission function's allowed permission list" as the extension path, but the implementation uses a new has_repo_permission function. The Note annotation correctly documents the evolution and has_write_permission is preserved as a backward-compatible wrapper.

  • [stale-doc] docs/ADRs/0059-public-mint-mode-with-wildcard-allowlists.md:108 — States "agent runs still require write permission at the dispatch layer" which is now incomplete — observation stages accept triage permission. Since ADR 0059 is an accepted ADR, a Note annotation would be the appropriate mechanism.

  • [stale-doc] docs/ADRs/0068-public-community-mint-architecture.md:77 — States "mint openness does not bypass write checks at dispatch" which is now incomplete since observation stages use triage-level checks. A Note annotation would be appropriate.

  • [comment-style] .github/workflows/reusable-dispatch.yml:128 — New function-level comments use abbreviated semicolon-separated style, departing from the existing multi-line descriptive comment style used by other functions in this file.

  • [function-declaration-style] .github/workflows/reusable-dispatch.yml — The new has_write_permission wrapper uses single-line function syntax while all other function declarations in this file use the multi-line pattern.

Previous run (2)

Review

Findings

Medium

Low

  • [permission-expansion] .github/workflows/reusable-dispatch.yml — The /fs-triage and /fs-review slash commands and the issues.opened, issues.edited, and pull_request_target.opened/synchronize/ready_for_review event paths now accept users with the GitHub triage role (previously required write+). This is an intentional expansion per ADR 0054 and issue dispatch: relax authorization gate to Triage role for triage stage and auto-review #5223. Observation stages are read-only and do not mint write tokens, so the reduced privilege floor is proportionate.

  • [ungated-dispatch] .github/workflows/reusable-dispatch.yml:281 — The pull_request_target.closed → retro path remains intentionally ungated. The diff adds a comment documenting this as intentional per ADR 0054 / dispatch: relax authorization gate to Triage role for triage stage and auto-review #5223. The retro stage mints a retro role token with permissions: contents: read only.

  • [return-statement-consistency] .github/workflows/reusable-dispatch.yml:151 — The triage case branch uses bare [[ "${min}" == "triage" ]] without an explicit return, while the admin|maintain|write branch uses return 0 and the default branch uses return 1. Functionally correct (the [[ exit status becomes the function return) but inconsistent with the explicit return style used in adjacent branches.

  • [stale-doc] docs/ADRs/0059-public-mint-mode-with-wildcard-allowlists.md:108 — States "agent runs still require write permission at the dispatch layer" which is now incomplete — observation stages accept triage permission. Since ADR 0059 is an accepted ADR, a Note annotation would be the appropriate mechanism.

  • [stale-doc] docs/ADRs/0068-public-community-mint-architecture.md:77 — States "mint openness does not bypass write checks at dispatch" which is now incomplete since observation stages use triage-level checks. A Note annotation would be appropriate.

Previous run (3)

Review

Findings

Medium

Low

  • [permission-expansion] .github/workflows/reusable-dispatch.yml — The /fs-triage and /fs-review slash commands now accept users with the GitHub triage role (previously required write+). This is an intentional expansion per ADR 0054 and issue dispatch: relax authorization gate to Triage role for triage stage and auto-review #5223. Observation stages are read-only and do not mint write tokens, so the reduced privilege floor is proportionate.

  • [permission-expansion] .github/workflows/reusable-dispatch.yml — The issues.opened, issues.edited, and pull_request_target.opened/synchronize/ready_for_review event paths now accept triage-role users for observation stages. Same risk profile as the slash command expansion.

  • [stale-doc] docs/ADRs/0059-public-mint-mode-with-wildcard-allowlists.md:108 — States "agent runs still require write permission at the dispatch layer" which is now incomplete — observation stages accept triage permission. Since ADR 0059 is an accepted ADR, a Note annotation would be the appropriate mechanism.

  • [stale-doc] docs/ADRs/0068-public-community-mint-architecture.md:77 — States "mint openness does not bypass write checks at dispatch" which is now incomplete since observation stages use triage-level checks. A Note annotation would be appropriate.

Previous run (4)

Review

Findings

Medium

Low

  • [permission-expansion] .github/workflows/reusable-dispatch.yml — The /fs-triage and /fs-review slash commands now accept users with the GitHub triage role (previously required write+). This is an intentional expansion per ADR 0054 and issue dispatch: relax authorization gate to Triage role for triage stage and auto-review #5223. Observation stages are read-only and do not mint write tokens, so the reduced privilege floor is proportionate.

  • [permission-expansion] .github/workflows/reusable-dispatch.yml — The issues.opened, issues.edited, and pull_request_target.opened/synchronize/ready_for_review event paths now accept triage-role users for observation stages. Same risk profile as the slash command expansion.

  • [stale-reference] docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md — The ADR body references has_write_permission and a three-function layering (lines 104, 179), but the implementation now uses has_repo_permission(username, min) as the base with has_write_permission as a wrapper. The Note annotation added before Consequences correctly documents this evolution. No action needed — the ADR is an accepted point-in-time record.

  • [documentation-completeness] .github/workflows/reusable-dispatch.yml:128 — The has_repo_permission function header comment is more terse than the original has_write_permission header, but preserves the key rationale in its second line: "API resolves org membership regardless of visibility (gh-aw-mcpg#2862)."

  • [stale-doc] docs/ADRs/0059-public-mint-mode-with-wildcard-allowlists.md:108 — States "agent runs still require write permission at the dispatch layer" which is now incomplete — observation stages accept triage permission. Since ADR 0059 is an accepted ADR, a Note annotation would be the appropriate mechanism.

  • [stale-doc] docs/ADRs/0068-public-community-mint-architecture.md:77 — States "mint openness does not bypass write checks at dispatch" which is now incomplete since observation stages use triage-level checks. A Note annotation would be appropriate.

Previous run (5)

Review

Findings

Medium

  • [adr-immutability-violation] docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md:184 — The Consequences section of this accepted ADR has been substantively rewritten: the original "All dispatch paths require write-level repository permission" was changed to "All dispatch paths require repository permission at a stage-appropriate minimum." Per AGENTS.md, accepted ADRs are point-in-time records — their Consequences section should remain as originally written. The Note added before the Consequences section already documents the evolution for readers; the Consequences text should be reverted to its original wording.

  • [documentation-completeness] .github/workflows/reusable-dispatch.yml:128 — The has_repo_permission function header comment dropped the original rationale explaining why the collaborator permission API is used (org membership resolution regardless of visibility for private orgs) and the reference to github/gh-aw-mcpg#2862. This context explains a non-obvious API choice and is valuable for future maintainers.

  • [protected-path] .github/workflows/reusable-dispatch.yml — This PR modifies a file under the .github/ protected path. The PR links to issues dispatch: relax authorization gate to Triage role for triage stage and auto-review #5223 and Support configurable minimum permission level for /fs-review in reusable dispatch #5154, and the description explains the rationale for modifying the dispatch authorization logic. Human approval is always required for changes to governance and infrastructure files, regardless of context.

Low

  • [stale-reference] docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md:104 — The ADR body describes a three-function layering with has_write_permission(username) as the base, but the implementation now uses has_repo_permission(username, min) with four functions. Since the ADR is an accepted point-in-time record, the Note added before Consequences is the correct mechanism to document this evolution (and it does).

  • [stale-reference] docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md:179 — References "extending the has_write_permission function's allowed permission list" but the PR renamed the function to has_repo_permission. The Note provides context.

  • [comment-format] .github/workflows/reusable-dispatch.yml:128 — The inline comment uses #5223 / ADR 0054 format which differs from existing patterns in the file (e.g., See: <ref>, ADR <n>:).

  • [stale-doc] docs/ADRs/0059-public-mint-mode-with-wildcard-allowlists.md:108 — States "agent runs still require write permission at the dispatch layer" which is now incomplete. Since ADR 0059 is also an accepted ADR, a Note annotation (rather than a rewrite) would be the appropriate mechanism to document this nuance.

Previous run (6)

Review

Findings

Medium

  • [adr-immutability-violation] docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md:184 — The Consequences section of this accepted ADR has been substantively rewritten: the original "All dispatch paths require write-level repository permission" was changed to "All dispatch paths require repository permission at a stage-appropriate minimum." Per AGENTS.md, accepted ADRs are point-in-time records — their Consequences section should remain as originally written. The Note added before the Consequences section already documents the evolution for readers; the Consequences text should be reverted to its original wording.

  • [documentation-completeness] .github/workflows/reusable-dispatch.yml:128 — The has_repo_permission function header comment dropped the original rationale explaining why the collaborator permission API is used (org membership resolution regardless of visibility for private orgs) and the reference to github/gh-aw-mcpg#2862. This context explains a non-obvious API choice and is valuable for future maintainers.

  • [protected-path] .github/workflows/reusable-dispatch.yml — This PR modifies a file under the .github/ protected path. The PR links to issues dispatch: relax authorization gate to Triage role for triage stage and auto-review #5223 and Support configurable minimum permission level for /fs-review in reusable dispatch #5154, and the description explains the rationale for modifying the dispatch authorization logic. Human approval is always required for changes to governance and infrastructure files, regardless of context.

Low

  • [stale-reference] docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md:104 — The ADR body describes a three-function layering with has_write_permission(username) as the base, but the implementation now uses has_repo_permission(username, min) with four functions. Since the ADR is an accepted point-in-time record, the Note added before Consequences is the correct mechanism to document this evolution (and it does).

  • [stale-reference] docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md:179 — References "extending the has_write_permission function's allowed permission list" but the PR renamed the function to has_repo_permission. The Note provides context.

  • [comment-format] .github/workflows/reusable-dispatch.yml:128 — The inline comment uses #5223 / ADR 0054 format which differs from existing patterns in the file (e.g., See: <ref>, ADR <n>:).

  • [stale-doc] docs/ADRs/0059-public-mint-mode-with-wildcard-allowlists.md:108 — States "agent runs still require write permission at the dispatch layer" which is now incomplete. Since ADR 0059 is also an accepted ADR, a Note annotation (rather than a rewrite) would be the appropriate mechanism to document this nuance.

Previous run (7)

Review

Findings

Medium

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Jul 17, 2026
Block triage→fix escalation via review-bot changes_requested on
human PRs, restore the 475-line workflow size cap, and align ADR 0054
Consequences with stage-dependent auth.

Signed-off-by: Adam Scerra <ascerra@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 17, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 4:26 PM UTC · Ended 4:45 PM UTC
Commit: cc7a526 · View workflow run →

@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

Site preview

Preview: https://3538b882-site.fullsend-ai.workers.dev

Commit: 99d840d2fc155e3f6dbafa6a3f52dda61cba83c0

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Squad — 3 verified findings (MEDIUM)

Agents: 3 (Claude ×2, Grok) · Models: Claude, Grok


1. MEDIUM — Label-triggered ready-to-code bypasses per-stage authorization boundary

File: .github/workflows/reusable-dispatch.yml:255-256 (and scaffold equivalent)

The issues.labeled path sets STAGE="code" (mutation stage) when ready-to-code is applied, with no permission check. ADR 0054's Consequences section now claims "All dispatch paths require repository permission at a stage-appropriate minimum," but GitHub's triage role can apply labels — a triage-role collaborator could apply ready-to-code to trigger the code agent without write-level permission.

Pre-existing on main — unchanged by this PR — but the updated ADR language makes the gap visible.

Suggestion: Add is_event_actor_authorized "${EVENT_SENDER_LOGIN}" write to the ready-to-code labeled path, or update the ADR to acknowledge that label-triggered paths use a different authorization model. The ready-for-triage / ready-for-review label paths are fine (observation stages).

Flagged by: Claude (×2), Grok — consensus


2. MEDIUM — PR claims Closes #5154 but does not implement requested configurability

#5154 is titled "Support configurable minimum permission level for /fs-review in reusable dispatch" and proposes per-repo opt-in configurability (Option A). This PR implements a hardcoded platform-level floor (Option B). While Option B is valid, it locks every consumer repo into the triage floor for observation stages with no way to keep a stricter write-only gate.

Suggestion: Either keep #5154 open for the configurability aspect (only close #5223), or comment on #5154 explaining why Option B was chosen and whether per-repo override is planned as follow-up.

Flagged by: Claude (researcher) — single agent


3. MEDIUM — Retro stage on PR close has no permission check

File: .github/workflows/reusable-dispatch.yml:279

pull_request_target.closed unconditionally sets STAGE="retro" — any user who closes a PR triggers a retro run, including fork PR authors with no repo permission. While retro is read-only, it consumes API quota / LLM tokens and is inconsistent with the ADR's "all paths authorized" claim.

Pre-existing on main — unchanged by this PR.

Suggestion: Add if is_event_actor_authorized "${EVENT_SENDER_LOGIN}" triage; then STAGE="retro"; fi, or document this as an intentional exception.

Flagged by: Grok — single agent


Assisted-by: Claude (review), Grok (review)

@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure · Started 4:26 PM UTC · Completed 4:45 PM UTC
Commit: 0411414 · View workflow run →

Require write+ (or bot) for ready-to-code label dispatch, document
ungated retro-on-close as intentional, and restore ADR 0054
Consequences wording per immutability.

Signed-off-by: Adam Scerra <ascerra@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@ascerra

ascerra commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

@waynesun09 Addressed your Review Squad findings in 492e9ac:

  1. ready-to-code — gated to write+ labelers (or bots for agent handoff). Triage-only humans can no longer trigger the code agent via that label.
  2. Support configurable minimum permission level for /fs-review in reusable dispatch #5154 — kept open for configurability; PR now only Closes fullsend-ai/fullsend#5223. Updated the PR description accordingly.
  3. Retro on PR close — behavior unchanged; documented as an intentional ungated exception (read-only lifecycle accounting) in both dispatch workflows + the ADR 0054 note.

Also reverted the ADR Consequences rewrite per ADR immutability, and restored the collaborator-API rationale comment.

@ascerra

ascerra commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

Merged latest main into this branch (was 13 commits behind).

E2E failure on the previous run (TestAdminInstallUninstall) was minting-related — minted agent token contains unexpected characters for role triage — not dispatch-auth. Watching CI after the merge for a clean e2e pass.

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 17, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:33 PM UTC · Completed 5:52 PM UTC
Commit: 4a904bd · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@ascerra

ascerra commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

CI after merging main is green: e2e, behaviour, unit/functional/build/codecov all pass. Earlier e2e failure was a mint flake (unexpected characters for role triage), not related to the dispatch auth change.

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Squad — 1 new finding (MEDIUM)

Agents: 4 (Claude ×2, Grok, Codex) · Models: Claude, Grok, Codex
Consensus: 3/4 agents flagged the test precision issue independently

All 6 prior findings (3 from review squad round 1, 3 from qodo) confirmed addressed — not re-raised.

Comment thread internal/scaffold/workflow_call_alignment_test.go Outdated
Guard bot bypass and write-default min so the mutation-stage label
gate cannot silently regress to triage.

Signed-off-by: Adam Scerra <ascerra@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 17, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:49 PM UTC · Completed 7:04 PM UTC
Commit: 148260d · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Authorization boundary is correct — observation stages at triage+, mutation stages at write+, bot handoffs preserved, fail-closed on errors. All prior findings (11) addressed across 5 review rounds.

One remaining nit: the ADR 0054 Consequences rewrite ("write-level" → "write-level for mutation, triage+ for observation") is a substantive edit to an Accepted ADR per AGENTS.md policy. The Note annotation above Consequences already documents the evolution correctly — consider reverting the bullet to its original wording. Not blocking.

Assisted-by: Claude (review), Grok (review), Codex (review)

Resolve workflow_call_alignment_test.go conflict by keeping both
per-stage auth and PR-head resolution tests. Revert ADR 0054
Consequences wording per AGENTS.md immutability (Note remains).

Signed-off-by: Adam Scerra <ascerra@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@ascerra

ascerra commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

@waynesun09 Addressed your latest nit in 99d840d:

  • Reverted ADR 0054 Consequences to the original write-level wording (Accepted ADR immutability). The Note above Consequences still records the triage+/write+ evolution.
  • Merged latest main and resolved the workflow_call_alignment_test.go conflict by keeping both per-stage auth tests and the PR-head resolution tests.

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 20, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:29 PM UTC · Completed 8:46 PM UTC
Commit: 99d840d · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review comment for full details.

Comment thread .github/workflows/reusable-dispatch.yml
Comment thread docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md
Comment thread .github/workflows/reusable-dispatch.yml
@ascerra
ascerra enabled auto-merge July 20, 2026 21:02
@ascerra
ascerra added this pull request to the merge queue Jul 20, 2026
Merged via the queue into main with commit 3cfa255 Jul 20, 2026
33 of 36 checks passed
@ascerra
ascerra deleted the feat/5223-triage-role-auth branch July 20, 2026 21:16
@fullsend-ai-retro

fullsend-ai-retro Bot commented Jul 20, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 9:19 PM UTC · Completed 9:34 PM UTC
Commit: 99d840d · View workflow run →

@fullsend-ai-retro

Copy link
Copy Markdown

Retro: PR #5244 — Allow triage role for observation stages

PR lifecycle: 8 review agent runs (5 success, 2 failure), 3 human review rounds, 11 findings all resolved, merged after ~3 days.

What the review agent did well

  • Caught ADR 0054 immutability violation when the author substantively rewrote the Consequences section of an Accepted ADR
  • Found cross-document staleness in ADR 0059 and ADR 0068 that neither qodo nor the human reviewer caught
  • Consistently flagged the protected-path governance gate on .github/workflows/reusable-dispatch.yml
  • Identified the return-statement inconsistency in has_repo_permission (though under-rated its severity)

Key gap: missed the two most critical security findings

The review agent ran 8 times but never flagged the PR's two privilege escalation paths — the highest-impact findings in the entire review:

  1. Triage→fix escalation via pull_request_review routing (found by qodo): A triage-role user's /fs-review triggers a review, and changes_requested routes to STAGE="fix" (mutation) without re-checking the original PR author's permission level.
  2. Triage→code escalation via ready-to-code label (found by human reviewer waynesun09): GitHub triage-role users can apply labels, so ready-to-code would route to STAGE="code" (mutation) with no permission check.

Both required reasoning across multiple dispatch routing branches rather than analyzing each changed line in isolation. The review agent's security sub-agent analyzed the has_repo_permission function correctly but did not trace its implications through the broader dispatch routing graph.

Severity calibration gap

The review agent flagged the implicit [[ ]] return in the triage branch as a Low style consistency issue. The human reviewer recognized the same code as a Medium security boundary concern with a concrete exploit scenario (future editor adds a line → return value silently becomes 0 → triage users get mutation access). This pattern — style finding at a security boundary underrated — is worth noting for severity calibration efforts.

Infrastructure failures

  • Run 29596130764 (422 error): The post-review script got a GitHub API 422 when submitting inline comments. This corroborates fullsend#5140 (out-of-hunk findings cause 422).
  • Run 29659735836 (timeout + validation race): First iteration timed out at 20 minutes; validation ran immediately and failed because agent-result.json didn't exist yet. The retry completed successfully 17 minutes later with valid output, but the post-script was skipped. Additionally, cleanup hit a Go panic: errSymlink is not user-visible — the error type lacks a proper Error() method.

Evidence for existing issues (not filing new proposals)

Proposals filed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

requires-manual-review Review requires human judgment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

dispatch: relax authorization gate to Triage role for triage stage and auto-review

2 participants