From 09f98ac9c25818837f033812a8b27a145c1b5022 Mon Sep 17 00:00:00 2001 From: Kombiz Lavasany Date: Sun, 11 Oct 2026 07:14:41 -0500 Subject: [PATCH 1/2] fix(buzz-acp): accept write route policies on the shared backend HS-46's owner chose full task agents on secrets.ts, so the Buzz routes will declare repository_write/workspace_write. The shared backend now accepts every valid profile and side-effect class and refuses only unknown values at startup; the daemon still checks the exact route match. Co-Authored-By: Claude Opus 5.5 --- crates/buzz-acp/README.md | 8 +++---- crates/buzz-acp/src/config.rs | 44 +++++++++++++++++++++-------------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/crates/buzz-acp/README.md b/crates/buzz-acp/README.md index 53c7ebc561a..abbb4e10d40 100644 --- a/crates/buzz-acp/README.md +++ b/crates/buzz-acp/README.md @@ -315,10 +315,10 @@ Run one Buzz identity per process and give each identity its own admitted route. The existing `gateway` backend continues to use the legacy control socket. Every shared submit declares the route's policy, and the daemon refuses a -mismatch. Set `BUZZ_ACP_GATEWAY_PERMISSION_PROFILE` (`repository_read` or -`restricted`) and `BUZZ_ACP_GATEWAY_SIDE_EFFECT_CLASS` (`pure_read` or -`external_effects`) to exactly the values in the gateway's route -configuration. The shared backend is read-only; write profiles fail at startup. +mismatch. Set `BUZZ_ACP_GATEWAY_PERMISSION_PROFILE` (`repository_read`, +`repository_write` or `restricted`) and `BUZZ_ACP_GATEWAY_SIDE_EFFECT_CLASS` +(`pure_read`, `external_effects` or `workspace_write`) to exactly the values in +the gateway's route configuration. Unknown values fail at startup. The shared backend derives a stable submission ID from the queued Buzz event IDs, looks up that ID before submitting, and never resubmits after a lost diff --git a/crates/buzz-acp/src/config.rs b/crates/buzz-acp/src/config.rs index c3848428c84..af117d5a599 100644 --- a/crates/buzz-acp/src/config.rs +++ b/crates/buzz-acp/src/config.rs @@ -185,8 +185,8 @@ pub fn resolve_gateway_backend_full( })) } -/// `backend=shared` is read-only, like auto routes: write profiles and -/// effects are refused here rather than by the daemon at the first turn. The +/// Unknown values are refused here rather than by the daemon at the first +/// turn; the daemon still checks that they equal the admitted route's. The /// legacy backend derives its policy per job, so it rejects these options. fn resolve_shared_policy( shared: bool, @@ -214,16 +214,22 @@ fn resolve_shared_policy( .into(), )); }; - if !matches!(permission_profile, "repository_read" | "restricted") { + if !matches!( + permission_profile, + "repository_read" | "repository_write" | "restricted" + ) { return Err(ConfigError::Gateway(format!( - "backend=shared permission profile '{permission_profile}' is not read-only \ - (expected repository_read or restricted)" + "unknown backend=shared permission profile '{permission_profile}' \ + (expected repository_read, repository_write or restricted)" ))); } - if !matches!(side_effect_class, "pure_read" | "external_effects") { + if !matches!( + side_effect_class, + "pure_read" | "external_effects" | "workspace_write" + ) { return Err(ConfigError::Gateway(format!( - "backend=shared side-effect class '{side_effect_class}' is not read-only \ - (expected pure_read or external_effects)" + "unknown backend=shared side-effect class '{side_effect_class}' \ + (expected pure_read, external_effects or workspace_write)" ))); } Ok(Some(SharedPolicy { @@ -808,12 +814,14 @@ pub struct CliArgs { pub gateway_auto_route_runtime: Option, /// Permission profile each shared submit declares (backend = shared). - /// Must equal the admitted route's: `repository_read` or `restricted`. + /// Must equal the admitted route's: `repository_read`, `repository_write` + /// or `restricted`. #[arg(long, env = "BUZZ_ACP_GATEWAY_PERMISSION_PROFILE")] pub gateway_permission_profile: Option, /// Side-effect class each shared submit declares (backend = shared). - /// Must equal the admitted route's: `pure_read` or `external_effects`. + /// Must equal the admitted route's: `pure_read`, `external_effects` or + /// `workspace_write`. #[arg(long, env = "BUZZ_ACP_GATEWAY_SIDE_EFFECT_CLASS")] pub gateway_side_effect_class: Option, } @@ -4099,15 +4107,17 @@ mod gateway_backend_tests { } #[test] - fn shared_policy_refuses_write_declarations() { - assert!( - resolve_shared_policy(true, Some("repository_write"), Some("external_effects")) - .is_err() - ); - assert!( - resolve_shared_policy(true, Some("repository_read"), Some("workspace_write")).is_err() + fn shared_policy_accepts_write_routes_and_refuses_unknown_values() { + assert_eq!( + resolve_shared_policy(true, Some("repository_write"), Some("workspace_write")).unwrap(), + Some(SharedPolicy { + permission_profile: "repository_write".into(), + side_effect_class: "workspace_write".into(), + }) ); assert!(resolve_shared_policy(true, Some("restricted"), Some("pure_read")).is_ok()); + assert!(resolve_shared_policy(true, Some("repo_write"), Some("workspace_write")).is_err()); + assert!(resolve_shared_policy(true, Some("repository_read"), Some("writes")).is_err()); } #[test] From 40e6291f59b9d2b5d64b0300386e233fdf022bff Mon Sep 17 00:00:00 2001 From: Kombiz Lavasany Date: Sun, 11 Oct 2026 07:23:16 -0500 Subject: [PATCH 2/2] docs(buzz-acp): note that shared write routes need a prompt-free agent Review follow-up. The shared backend cancels every permission request, so a write route can edit files only when the gateway's agent command already runs without prompts. Co-Authored-By: Claude Opus 5.5 --- crates/buzz-acp/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/buzz-acp/README.md b/crates/buzz-acp/README.md index abbb4e10d40..0084babfe32 100644 --- a/crates/buzz-acp/README.md +++ b/crates/buzz-acp/README.md @@ -326,7 +326,8 @@ acknowledgment. It attaches to the daemon's ACP stream, reconnects by task ID and sequence, and sends an owner's `!cancel` through the active attachment. It collects streamed text into one threaded Buzz reply after the turn completes. Permission requests are canceled because Buzz has no interactive approval UI -for this backend. A missed stream segment or uncertain relay publish is +for this backend, so a write route edits files only when the gateway's agent +command already runs without permission prompts. A missed stream segment or uncertain relay publish is reported for operator reconciliation; the harness does not replay the prompt or publish a guessed reply. A request the daemon provably never admitted, such as one still refused as `busy` at the turn deadline or refused by policy, is