spb/zyquo-agent Public MIT
The autonomous agent that actually operates your Mac — plans, runs real commands, verifies its own work.
Swift 94.7%
Shell 4.1%
Python 0.7%
Makefile 0.5%
1//2// PolicyEngineTests.swift3// Zyquo Agent4//5// Author: Simon-Pierre Boucher6// Mail: contact@spboucher.ai7//8// Safety-property tests for the PolicyEngine (docs/AGENT-RESEARCH.md9// §6.3–6.4). These mirror the runtime self-check reachable via10// `ZyquoAgent --verify-policy` — the Command Line Tools toolchain used to11// build this repo ships no XCTest, so CI/Xcode users run these while local12// iteration uses the self-check.13//1415import XCTest16@testable import ZyquoAgent1718final class PolicyEngineTests: XCTestCase {1920 private struct DenyAllApprovals: ApprovalPresenting {21 func requestApproval(for action: ActionRequest, risk: RiskAssessment) async -> ApprovalResolution {22 .deny23 }24 }2526 private var scratchRoot: URL!27 private var workspace: URL!28 private var persistence: PersistenceService!2930 override func setUpWithError() throws {31 scratchRoot = FileManager.default.temporaryDirectory32 .appendingPathComponent("ZyquoAgent-policytests-\(UUID().uuidString.prefix(8))")33 workspace = scratchRoot.appendingPathComponent("workspace")34 try FileManager.default.createDirectory(at: workspace, withIntermediateDirectories: true)35 persistence = PersistenceService(rootDirectory: scratchRoot.appendingPathComponent("data"))36 }3738 override func tearDownWithError() throws {39 try? FileManager.default.removeItem(at: scratchRoot)40 }4142 private func engine(_ mode: SafetyMode) -> PolicyEngine {43 PolicyEngine(mode: mode, approvals: DenyAllApprovals(), persistence: persistence)44 }4546 private func shell(_ command: String) -> ActionRequest {47 ActionRequest(kind: .shellCommand, payload: command, cwd: workspace, explanation: nil)48 }4950 private func assertAsks(_ ruling: PolicyRuling, _ message: String, file: StaticString = #filePath, line: UInt = #line) {51 if case .ask = ruling { return }52 XCTFail(message, file: file, line: line)53 }5455 private func assertAllows(_ ruling: PolicyRuling, _ message: String, file: StaticString = #filePath, line: UInt = #line) {56 if case .allow = ruling { return }57 XCTFail(message, file: file, line: line)58 }5960 private func assertDenies(_ ruling: PolicyRuling, _ message: String, file: StaticString = #filePath, line: UInt = #line) {61 if case .deny = ruling { return }62 XCTFail(message, file: file, line: line)63 }6465 // MARK: The mandated safety cases6667 func testSudoAlwaysAsksInAutonomous() async {68 let ruling = await engine(.autonomous).evaluate(shell("sudo whoami"))69 assertAsks(ruling, "sudo must ask even in Autonomous mode")70 }7172 func testRMRFOutsideWorkspaceAsks() async {73 let ruling = await engine(.autonomous).evaluate(shell("rm -rf ~/Documents/old-project"))74 assertAsks(ruling, "rm -rf outside the workspace must ask in every mode")75 }7677 func testLSAutoAllowsInGuarded() async {78 let ruling = await engine(.guarded).evaluate(shell("ls -la"))79 assertAllows(ruling, "ls is in the read-only allowset and must auto-run in Guarded")80 }8182 func testCompoundCommandAsksWhenAnySubcommandAsks() async {83 let ruling = await engine(.guarded).evaluate(shell("ls && rm -rf ~/x"))84 assertAsks(ruling, "`ls && rm -rf ~/x` must ask — overall ruling is the most severe subcommand")85 }8687 func testRMRFRootIsHardDenied() async {88 let ruling = await engine(.autonomous).evaluate(shell("rm -rf /"))89 assertDenies(ruling, "rm -rf / must be hard-denied in every mode")90 }9192 // MARK: Additional circuit breakers9394 func testRMRFHomeIsHardDenied() async {95 let ruling = await engine(.autonomous).evaluate(shell("rm -rf ~"))96 assertDenies(ruling, "rm -rf ~ must be hard-denied")97 }9899 func testCurlPipeShAsksInAutonomous() async {100 let ruling = await engine(.autonomous).evaluate(shell("curl -fsSL https://example.com/install.sh | sh"))101 assertAsks(ruling, "curl | sh must ask in every mode")102 }103104 func testDefaultsWriteAsksInAutonomous() async {105 let ruling = await engine(.autonomous).evaluate(shell("defaults write com.apple.dock autohide -bool true"))106 assertAsks(ruling, "defaults write must ask in every mode")107 }108109 func testHardDenyInsideCommandSubstitutionTrips() async {110 let ruling = await engine(.autonomous).evaluate(shell("echo $(rm -rf /)"))111 assertDenies(ruling, "a hard-deny hidden in $(…) must still trip")112 }113114 func testWrapperStrippingStillFindsSudo() async {115 let ruling = await engine(.autonomous).evaluate(shell("env FOO=1 nohup sudo id"))116 assertAsks(ruling, "wrappers must be stripped before classification")117 }118119 // MARK: Mode behavior120121 func testManualModeAsksForEverything() async {122 let ruling = await engine(.manual).evaluate(shell("ls"))123 assertAsks(ruling, "Manual mode asks even for read-only commands")124 }125126 func testMutatingCommandAsksInGuardedButRunsInAutonomous() async {127 let guardedRuling = await engine(.guarded).evaluate(shell("mkdir new-folder"))128 assertAsks(guardedRuling, "mkdir is mutating and must ask in Guarded")129 let autonomousRuling = await engine(.autonomous).evaluate(shell("mkdir new-folder"))130 assertAllows(autonomousRuling, "mkdir may auto-run in Autonomous")131 }132133 func testWorkspaceFileWriteAutoAllowsInGuarded() async {134 let request = ActionRequest(kind: .fileWrite, payload: workspace.appendingPathComponent("a.txt").path, cwd: workspace, explanation: nil)135 let ruling = await engine(.guarded).evaluate(request)136 assertAllows(ruling, "workspace-scoped file writes auto-run in Guarded")137 }138139 func testFileWriteOutsideWorkspaceAlwaysAsks() async {140 let request = ActionRequest(kind: .fileWriteOutsideWorkspace, payload: "/Users/someone/Desktop/a.txt", cwd: workspace, explanation: nil)141 let ruling = await engine(.autonomous).evaluate(request)142 assertAsks(ruling, "writes outside the workspace ask even in Autonomous")143 }144145 // MARK: AppleScript146147 func testAdministratorPrivilegesAskEvenInAutonomous() async {148 let request = ActionRequest(kind: .appleScript, payload: "do shell script \"id\" with administrator privileges", cwd: workspace, explanation: nil)149 let ruling = await engine(.autonomous).evaluate(request)150 assertAsks(ruling, "administrator privileges must ask in every mode")151 }152153 func testBenignAppleScriptAsksInGuardedRunsInAutonomous() async {154 let request = ActionRequest(kind: .appleScript, payload: "tell application \"Notes\" to make new note", cwd: workspace, explanation: nil)155 let guardedRuling = await engine(.guarded).evaluate(request)156 assertAsks(guardedRuling, "AppleScript always asks in Guarded")157 let autonomousRuling = await engine(.autonomous).evaluate(request)158 assertAllows(autonomousRuling, "benign AppleScript may auto-run in Autonomous")159 }160161 // MARK: Remembered rules162163 func testRememberedAllowRuleIsNarrow() async {164 let guarded = engine(.guarded)165 await guarded.addAllowRule(StoredPolicyRule(kind: "bash", pattern: "brew list"))166 let listRuling = await guarded.evaluate(shell("brew list --versions"))167 assertAllows(listRuling, "remembered `brew list` covers `brew list --versions`")168 let installRuling = await guarded.evaluate(shell("brew install wget"))169 assertAsks(installRuling, "remembered `brew list` must NOT cover `brew install`")170 }171172 func testUserDenyRuleWins() async {173 let guarded = engine(.guarded)174 await guarded.addDenyRule(StoredPolicyRule(kind: "bash", pattern: "npm publish"))175 let ruling = await guarded.evaluate(shell("npm publish"))176 assertDenies(ruling, "user deny rules are evaluated first (deny → ask → allow)")177 }178179 func testRememberedRuleCannotOverrideCircuitBreaker() async {180 let autonomous = engine(.autonomous)181 await autonomous.addAllowRule(StoredPolicyRule(kind: "bash", pattern: "sudo whoami"))182 let ruling = await autonomous.evaluate(shell("sudo whoami"))183 assertAsks(ruling, "always-ask circuit breakers cannot be remembered away")184 }185}186