From 996296dfd975f8cd393b6675722f2b546193eaa3 Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Tue, 30 Jun 2026 13:32:23 -0400 Subject: [PATCH 1/5] docs: add ADR 0062 for auto-merge Proposes a separate fullsend-ai-merge app with contents:write so bot approvals count toward branch protection, combined with blank-owner CODEOWNERS entries for dependency files. Assisted-by: Claude claude-opus-4-6 Signed-off-by: Ralph Bean --- docs/ADRs/0062-auto-merge.md | 73 ++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 docs/ADRs/0062-auto-merge.md diff --git a/docs/ADRs/0062-auto-merge.md b/docs/ADRs/0062-auto-merge.md new file mode 100644 index 0000000000..8cf07115f1 --- /dev/null +++ b/docs/ADRs/0062-auto-merge.md @@ -0,0 +1,73 @@ +--- +title: "62. Auto-merge" +status: Accepted +relates_to: + - agent-architecture + - security-threat-model +topics: + - github-apps + - least-privilege + - auto-merge +--- + +# 62. Auto-merge + +Date: 2026-06-30 + +## Status + +Accepted + +## Context + +Our review bot (`fullsend-ai-review`) can approve PRs, but GitHub only counts +an approval toward branch protection when the reviewer has write access +(`contents: write`) to the repository. The review app intentionally has +`contents: read` — giving it write access would violate the least-privilege +boundary established in [ADR 7](0007-per-role-github-apps.md). + +We also need the bot's approval to satisfy CODEOWNERS for dependency files +(`go.mod`, `go.sum`). GitHub App bots cannot be listed as CODEOWNERS entries — +they are not "users" in GitHub's model. + +## Options + +### A. Add `contents: write` to the review app + +Collapses the permission boundary between review and implementation roles. +Every repo that installs the review app would grant it write access it doesn't +otherwise need. + +### B. Create a separate `fullsend-ai-merge` app + +A new app with `contents: write` and `pull_requests: write`. Repos opt in to +auto-merge by installing this app instead of (or alongside) the review app. +The trust escalation is explicit and visible in GitHub's UI. + +### C. Exempt dependency files via blank-owner CODEOWNERS entries + +Use [blank-owner entries](https://github.com/orgs/community/discussions/23064) +to remove the CODEOWNERS requirement for specific files. This is orthogonal to +the app permission question — it addresses the CODEOWNERS gate, not the +approval-counts gate. + +## Decision + +Use options B and C together: + +1. **Create a `fullsend-ai-merge` app** with `contents: write` and + `pull_requests: write`. Its approvals count toward branch protection. Repos + that want bot-driven auto-merge install this app; repos that only want + informational reviews keep `fullsend-ai-review`. + +2. **Use blank-owner CODEOWNERS entries** for files that bots should be able to + approve (starting with `go.mod` and `go.sum`). This removes the CODEOWNERS + gate for those files without affecting other paths. + +## Consequences + +- The review app stays read-only — no permission creep. +- Auto-merge is an explicit opt-in per repo, visible in the GitHub App installation list. +- Blank-owner CODEOWNERS entries remove code-owner review for the listed files; CI status checks remain the primary safety gate for those paths. +- Adding a new app increases the number of apps to manage and enroll per org. +- The merge app's `contents: write` permission is a higher-value target if compromised — the same mitigations from [ADR 7](0007-per-role-github-apps.md) apply (repo-scoped install, PEM in config repo secrets). From 5921a16013f455d7396179c98e44843f989b73fd Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Mon, 13 Jul 2026 14:06:20 -0400 Subject: [PATCH 2/5] =?UTF-8?q?docs:=20revise=20ADR=200062=20=E2=80=94=20e?= =?UTF-8?q?nv-var=20toggle=20over=20separate=20agent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reframe the auto-merge decision: instead of a standalone merge agent, add a new app identity (fullsend-ai-merge) and teach the existing review agent to auto-merge behind REVIEW_AUTO_MERGE. This is the more reversible path — standing up a dedicated agent later is not made harder by this decision. Also links Context to problem docs per ADR conventions. Assisted-by: Claude Opus 4.6 Signed-off-by: Ralph Bean --- docs/ADRs/0062-auto-merge.md | 66 +++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/docs/ADRs/0062-auto-merge.md b/docs/ADRs/0062-auto-merge.md index 8cf07115f1..fa84563a53 100644 --- a/docs/ADRs/0062-auto-merge.md +++ b/docs/ADRs/0062-auto-merge.md @@ -22,13 +22,14 @@ Accepted Our review bot (`fullsend-ai-review`) can approve PRs, but GitHub only counts an approval toward branch protection when the reviewer has write access -(`contents: write`) to the repository. The review app intentionally has -`contents: read` — giving it write access would violate the least-privilege -boundary established in [ADR 7](0007-per-role-github-apps.md). +(`contents: write`). The review app has `contents: read` — giving it write +access would violate the least-privilege boundary in +[ADR 7](0007-per-role-github-apps.md). See +[agent-architecture.md](../problems/agent-architecture.md) and +[security-threat-model.md](../problems/security-threat-model.md). We also need the bot's approval to satisfy CODEOWNERS for dependency files -(`go.mod`, `go.sum`). GitHub App bots cannot be listed as CODEOWNERS entries — -they are not "users" in GitHub's model. +(`go.mod`, `go.sum`). GitHub App bots cannot appear as CODEOWNERS entries. ## Options @@ -38,36 +39,53 @@ Collapses the permission boundary between review and implementation roles. Every repo that installs the review app would grant it write access it doesn't otherwise need. -### B. Create a separate `fullsend-ai-merge` app +### B. Create a separate merge agent -A new app with `contents: write` and `pull_requests: write`. Repos opt in to -auto-merge by installing this app instead of (or alongside) the review app. -The trust escalation is explicit and visible in GitHub's UI. +A new agent with its own harness config, slug, and app identity +(`fullsend-ai-merge`). Repos opt in by installing the merge app. Clean +separation, but duplicates orchestration logic and adds a new agent to +maintain. -### C. Exempt dependency files via blank-owner CODEOWNERS entries +### C. New app identity, same review agent, env-var toggle + +Create a `fullsend-ai-merge` app with `contents: write` and +`pull_requests: write`, but teach the existing review agent to auto-merge when +an env var (`REVIEW_AUTO_MERGE=true`) is set. Repos that want auto-merge +install the merge app and set the var; repos that want read-only reviews keep +`fullsend-ai-review` unchanged. + +### D. Exempt dependency files via blank-owner CODEOWNERS entries Use [blank-owner entries](https://github.com/orgs/community/discussions/23064) -to remove the CODEOWNERS requirement for specific files. This is orthogonal to -the app permission question — it addresses the CODEOWNERS gate, not the -approval-counts gate. +to remove the CODEOWNERS requirement for specific files. Orthogonal to the app +permission question — addresses the CODEOWNERS gate, not the approval gate. ## Decision -Use options B and C together: +Use options C and D together. 1. **Create a `fullsend-ai-merge` app** with `contents: write` and - `pull_requests: write`. Its approvals count toward branch protection. Repos - that want bot-driven auto-merge install this app; repos that only want - informational reviews keep `fullsend-ai-review`. + `pull_requests: write`. Repos that want bot-driven auto-merge install this + app alongside or instead of `fullsend-ai-review`. + +2. **Add auto-merge behavior to the review agent**, gated behind + `REVIEW_AUTO_MERGE`. When the var is set and the review verdict is + `approve`, the post-script enables GitHub auto-merge on the PR. No new + agent harness — same `review.yaml`, same skills, same post-script. + +3. **Use blank-owner CODEOWNERS entries** for files bots should be able to + approve (starting with `go.mod` and `go.sum`). -2. **Use blank-owner CODEOWNERS entries** for files that bots should be able to - approve (starting with `go.mod` and `go.sum`). This removes the CODEOWNERS - gate for those files without affecting other paths. +This is the most **reversible** path. If a dedicated merge agent turns out to +be the better UX, having once had a more capable review agent does not make +that harder — we stand up the new agent and deprecate the env var. Going the +other direction (standalone agent first, then collapsing back) would be. ## Consequences - The review app stays read-only — no permission creep. -- Auto-merge is an explicit opt-in per repo, visible in the GitHub App installation list. -- Blank-owner CODEOWNERS entries remove code-owner review for the listed files; CI status checks remain the primary safety gate for those paths. -- Adding a new app increases the number of apps to manage and enroll per org. -- The merge app's `contents: write` permission is a higher-value target if compromised — the same mitigations from [ADR 7](0007-per-role-github-apps.md) apply (repo-scoped install, PEM in config repo secrets). +- Auto-merge is opt-in per repo, visible in the GitHub App installation list. +- No new agent to maintain — auto-merge is a small addition to `post-review.sh`. +- If the env-var approach proves wrong, standing up a dedicated agent is not made harder by this decision. +- Blank-owner CODEOWNERS entries remove code-owner review for listed files; CI status checks remain the primary safety gate. +- The merge app's `contents: write` is a higher-value target if compromised — same mitigations from [ADR 7](0007-per-role-github-apps.md) apply. From 50f5e876f4119fbd456eba1856f69bb335e1a7ea Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Mon, 13 Jul 2026 14:31:15 -0400 Subject: [PATCH 3/5] =?UTF-8?q?docs:=20renumber=20ADR=200062=20=E2=86=92?= =?UTF-8?q?=200071=20to=20avoid=20collision?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 0062 is taken on main by dispatch-version-skew.md. Renumber to 0071, the next available number considering main and open PRs. Assisted-by: Claude Opus 4.6 Signed-off-by: Ralph Bean --- docs/ADRs/{0062-auto-merge.md => 0071-auto-merge.md} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename docs/ADRs/{0062-auto-merge.md => 0071-auto-merge.md} (98%) diff --git a/docs/ADRs/0062-auto-merge.md b/docs/ADRs/0071-auto-merge.md similarity index 98% rename from docs/ADRs/0062-auto-merge.md rename to docs/ADRs/0071-auto-merge.md index fa84563a53..7366c242b0 100644 --- a/docs/ADRs/0062-auto-merge.md +++ b/docs/ADRs/0071-auto-merge.md @@ -1,5 +1,5 @@ --- -title: "62. Auto-merge" +title: "71. Auto-merge" status: Accepted relates_to: - agent-architecture @@ -10,7 +10,7 @@ topics: - auto-merge --- -# 62. Auto-merge +# 71. Auto-merge Date: 2026-06-30 From 0a7bd323aef22bf0e0957c09c3ef15cd67b8daad Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Mon, 13 Jul 2026 15:08:59 -0400 Subject: [PATCH 4/5] docs: address review feedback on ADR 0071 - Complete the sentence fragment in the reversibility rationale - Zero-pad ADR cross-references to match established convention - Add token handoff section: merge app token minted when REVIEW_AUTO_MERGE is set, fail-hard on misconfiguration - Add security considerations: credential isolation, prompt injection blast radius, CODEOWNERS bypass scope, env var delivery protection - Note that implementation in review.yaml and post-review.sh is deferred to a follow-up PR Signed-off-by: Ralph Bean Assisted-by: Claude Opus 4.6 Signed-off-by: Ralph Bean --- docs/ADRs/0071-auto-merge.md | 40 +++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/docs/ADRs/0071-auto-merge.md b/docs/ADRs/0071-auto-merge.md index 7366c242b0..6c2b1546be 100644 --- a/docs/ADRs/0071-auto-merge.md +++ b/docs/ADRs/0071-auto-merge.md @@ -24,7 +24,7 @@ Our review bot (`fullsend-ai-review`) can approve PRs, but GitHub only counts an approval toward branch protection when the reviewer has write access (`contents: write`). The review app has `contents: read` — giving it write access would violate the least-privilege boundary in -[ADR 7](0007-per-role-github-apps.md). See +[ADR 0007](0007-per-role-github-apps.md). See [agent-architecture.md](../problems/agent-architecture.md) and [security-threat-model.md](../problems/security-threat-model.md). @@ -79,7 +79,41 @@ Use options C and D together. This is the most **reversible** path. If a dedicated merge agent turns out to be the better UX, having once had a more capable review agent does not make that harder — we stand up the new agent and deprecate the env var. Going the -other direction (standalone agent first, then collapsing back) would be. +other direction (standalone agent first, then collapsing back) would be harder +because the standalone agent accumulates its own orchestration logic that must +be reconciled. + +### Token handoff + +When `REVIEW_AUTO_MERGE` is set, the harness mints a token from the +`fullsend-ai-merge` app instead of `fullsend-ai-review`. If the merge app is +not installed on the repo, the agent fails hard — this is a misconfiguration. +Normal review operations (reading code, posting comments) do not change; only +the auto-merge step requires the elevated token. + +### Security considerations + +- **Credential isolation.** The merge app token is minted per-run and scoped to + the triggering repo. The review agent never holds both the merge token and a + provider credential simultaneously — sandbox isolation + ([ADR 0017](0017-credential-isolation-for-sandboxed-agents.md)) keeps them + separated. +- **Prompt injection blast radius.** A compromised review agent with the merge + token can enable auto-merge on the PR it was invoked for. It cannot merge + arbitrary PRs — branch protection (required reviewers, status checks) still + gates the actual merge. The blast radius is one PR, same as today. +- **CODEOWNERS bypass scope.** Blank-owner entries only remove the code-owner + review requirement for the listed paths (`go.mod`, `go.sum`). CI status + checks remain the primary safety gate for those files. +- **`REVIEW_AUTO_MERGE` delivery protection.** The env var is set in the + repo's workflow file, not by the agent. An attacker who can modify the + workflow file already has write access to the repo. + +### Implementation deferral + +Implementation of `REVIEW_AUTO_MERGE` in `review.yaml` and `post-review.sh` +is deferred to a follow-up PR. This ADR records the decision; the follow-up +wires it up. ## Consequences @@ -88,4 +122,4 @@ other direction (standalone agent first, then collapsing back) would be. - No new agent to maintain — auto-merge is a small addition to `post-review.sh`. - If the env-var approach proves wrong, standing up a dedicated agent is not made harder by this decision. - Blank-owner CODEOWNERS entries remove code-owner review for listed files; CI status checks remain the primary safety gate. -- The merge app's `contents: write` is a higher-value target if compromised — same mitigations from [ADR 7](0007-per-role-github-apps.md) apply. +- The merge app's `contents: write` is a higher-value target if compromised — same mitigations from [ADR 0007](0007-per-role-github-apps.md) apply. From 3a3507a9a9a38aa786f58b62a3c7d8c088e6e0a6 Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Mon, 13 Jul 2026 15:10:08 -0400 Subject: [PATCH 5/5] docs: note why this ADR lives here despite agent repo move The review agent moved to fullsend-ai/agents, but auto-merge touches core architectural concerns (app identity boundaries, credential isolation, CODEOWNERS policy) that belong in the central ADR corpus. Signed-off-by: Ralph Bean Assisted-by: Claude Opus 4.6 Signed-off-by: Ralph Bean --- docs/ADRs/0071-auto-merge.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/ADRs/0071-auto-merge.md b/docs/ADRs/0071-auto-merge.md index 6c2b1546be..83bfe618e8 100644 --- a/docs/ADRs/0071-auto-merge.md +++ b/docs/ADRs/0071-auto-merge.md @@ -31,6 +31,12 @@ access would violate the least-privilege boundary in We also need the bot's approval to satisfy CODEOWNERS for dependency files (`go.mod`, `go.sum`). GitHub App bots cannot appear as CODEOWNERS entries. +The review agent implementation now lives in `fullsend-ai/agents`, but +auto-merge touches core design concerns — GitHub App identity boundaries, +credential isolation, and CODEOWNERS policy — that are central architectural +decisions. This ADR records the decision here; implementation follows in the +agents repo. + ## Options ### A. Add `contents: write` to the review app