fix(policy): treat git branch listing as read-only operation
Remove 'branch' from requires_write_access() to resolve the contradiction where branch listing was classified as both read-only and write-requiring. Branch listing only enumerates local refs and has no side effects, so it should remain available under ReadOnly autonomy mode. Add regression tests: - branch_is_not_write_gated: verifies classification consistency - allows_branch_listing_in_readonly_mode: verifies end-to-end execution under ReadOnly autonomy - is_read_only_detection: now explicitly asserts branch is read-only Resolves zeroclaw-labs/zeroclaw#612 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
b5e1c3a8f5
commit
fbc26be7af
1 changed files with 40 additions and 1 deletions
|
|
@ -53,7 +53,7 @@ impl GitOperationsTool {
|
||||||
fn requires_write_access(&self, operation: &str) -> bool {
|
fn requires_write_access(&self, operation: &str) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
operation,
|
operation,
|
||||||
"commit" | "add" | "checkout" | "branch" | "stash" | "reset" | "revert"
|
"commit" | "add" | "checkout" | "stash" | "reset" | "revert"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -666,6 +666,16 @@ mod tests {
|
||||||
assert!(!tool.requires_write_access("log"));
|
assert!(!tool.requires_write_access("log"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn branch_is_not_write_gated() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let tool = test_tool(tmp.path());
|
||||||
|
|
||||||
|
// Branch listing is read-only; it must not require write access
|
||||||
|
assert!(!tool.requires_write_access("branch"));
|
||||||
|
assert!(tool.is_read_only("branch"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn is_read_only_detection() {
|
fn is_read_only_detection() {
|
||||||
let tmp = TempDir::new().unwrap();
|
let tmp = TempDir::new().unwrap();
|
||||||
|
|
@ -674,6 +684,7 @@ mod tests {
|
||||||
assert!(tool.is_read_only("status"));
|
assert!(tool.is_read_only("status"));
|
||||||
assert!(tool.is_read_only("diff"));
|
assert!(tool.is_read_only("diff"));
|
||||||
assert!(tool.is_read_only("log"));
|
assert!(tool.is_read_only("log"));
|
||||||
|
assert!(tool.is_read_only("branch"));
|
||||||
|
|
||||||
assert!(!tool.is_read_only("commit"));
|
assert!(!tool.is_read_only("commit"));
|
||||||
assert!(!tool.is_read_only("add"));
|
assert!(!tool.is_read_only("add"));
|
||||||
|
|
@ -708,6 +719,34 @@ mod tests {
|
||||||
.contains("higher autonomy"));
|
.contains("higher autonomy"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn allows_branch_listing_in_readonly_mode() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
// Initialize a git repository so the command can succeed
|
||||||
|
std::process::Command::new("git")
|
||||||
|
.args(["init"])
|
||||||
|
.current_dir(tmp.path())
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let security = Arc::new(SecurityPolicy {
|
||||||
|
autonomy: AutonomyLevel::ReadOnly,
|
||||||
|
..SecurityPolicy::default()
|
||||||
|
});
|
||||||
|
let tool = GitOperationsTool::new(security, tmp.path().to_path_buf());
|
||||||
|
|
||||||
|
let result = tool
|
||||||
|
.execute(json!({"operation": "branch"}))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
// Branch listing must not be blocked by read-only autonomy
|
||||||
|
let error_msg = result.error.as_deref().unwrap_or("");
|
||||||
|
assert!(
|
||||||
|
!error_msg.contains("read-only") && !error_msg.contains("higher autonomy"),
|
||||||
|
"branch listing should not be blocked in read-only mode, got: {error_msg}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn allows_readonly_ops_in_readonly_mode() {
|
async fn allows_readonly_ops_in_readonly_mode() {
|
||||||
let tmp = TempDir::new().unwrap();
|
let tmp = TempDir::new().unwrap();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue