Files
openclaw/apps/macos/Tests/OpenClawIPCTests/ExecApprovalsStoreRefactorTests.swift
Peter Steinberger a8f8fd3256 fix(macos): prevent shared state in parallel test suites (#120912)
* fix(macos): isolate parallel test state

* chore(i18n): refresh native source inventory
2026-08-08 23:03:20 -07:00

1157 lines
47 KiB
Swift

import Foundation
import OpenClawKit
import SQLite3
import Testing
@testable import OpenClaw
@Suite(.serialized)
struct ExecApprovalsStoreRefactorTests {
private var realTemporaryDirectory: URL {
let path = FileManager().temporaryDirectory.path
if path.hasPrefix("/var/") {
return URL(fileURLWithPath: "/private\(path)", isDirectory: true)
}
return FileManager().temporaryDirectory.resolvingSymlinksInPath()
}
private func withTempStateDir(
seedCurrentApprovals: Bool = true,
_ body: @escaping @Sendable (URL) async throws -> Void) async throws
{
let root = self.realTemporaryDirectory
.appendingPathComponent("openclaw-state-\(UUID().uuidString)", isDirectory: true)
let stateDir = root.appendingPathComponent("state", isDirectory: true)
defer { try? FileManager().removeItem(at: root) }
if seedCurrentApprovals {
try Self.seedCurrentApprovalsFile(in: stateDir)
}
try await ExecApprovalsStore.withStateDirectory(stateDir) {
try await body(stateDir)
}
}
@Test
func `ensure store reuses socket token when unchanged`() async throws {
try await self.withTempStateDir { _ in
let first = ExecApprovalsStore.ensureFile()
let second = ExecApprovalsStore.ensureFile()
#expect(first.socket?.token == "test-token")
#expect(second.socket?.token == first.socket?.token)
}
}
@Test
func `omitted policy fields match TypeScript defaults`() async throws {
try await self.withTempStateDir { _ in
let resolved = ExecApprovalsStore.resolve(agentId: "main")
#expect(resolved.agent.security == .full)
#expect(resolved.agent.ask == .off)
#expect(resolved.agent.askFallback == .deny)
#expect(!resolved.agent.autoAllowSkills)
}
}
@Test
func `task scoped state directory survives detached async reads`() async throws {
try await self.withTempStateDir { stateDirectoryURL in
_ = try ExecApprovalsStore.updateDefaults { defaults in
defaults.security = .allowlist
defaults.ask = .onMiss
}.get()
let resolved = try await ExecApprovalsStore.resolveDefaultsAsyncResult().get()
#expect(resolved.security == .allowlist)
#expect(resolved.ask == .onMiss)
#expect(ExecApprovalsStore.databaseURL() == ExecApprovalsSQLiteStore.databaseURL(
stateDirectoryURL: stateDirectoryURL))
}
}
@Test
func `migration cache logs once and recovers after the legacy identity clears`() {
let stateDirectoryURL = URL(fileURLWithPath: "/tmp/openclaw-migration-cache-test")
let legacyFileURL = stateDirectoryURL.appendingPathComponent("exec-approvals.json")
let error = ExecApprovalsLegacyMigrationRequiredError(
stateDirectoryURL: stateDirectoryURL,
legacyFileURL: legacyFileURL)
var identity: ExecApprovalsMigrationRequiredCache.FileIdentity? = .init(
device: 1,
inode: 2,
modificationSeconds: 3,
modificationNanoseconds: 4)
var identityReadCount = 0
var events: [ExecApprovalsMigrationLogEvent] = []
let cache = ExecApprovalsMigrationRequiredCache(
identityReader: { _ in
identityReadCount += 1
return identity
},
onEvent: { events.append($0) })
cache.record(error)
#expect(cache.cachedError(stateDirectoryURL: stateDirectoryURL) == error)
#expect(cache.cachedError(stateDirectoryURL: stateDirectoryURL) == error)
#expect(identityReadCount == 3)
#expect(events == [.required(error)])
identity = nil
#expect(cache.cachedError(stateDirectoryURL: stateDirectoryURL) == nil)
cache.markResolved(stateDirectoryURL: stateDirectoryURL)
#expect(identityReadCount == 4)
#expect(events == [
.required(error),
.recovered(stateDirectoryPath: stateDirectoryURL.path),
])
}
@Test
func `legacy migration failure is typed and removal recovers without restart`() async throws {
try await self.withTempStateDir { stateDirectoryURL in
let legacyFileURL = stateDirectoryURL.appendingPathComponent("exec-approvals.json")
try FileManager.default.createDirectory(
at: stateDirectoryURL,
withIntermediateDirectories: true)
try Data(#"{"version":1,"agents":{}}"#.utf8).write(to: legacyFileURL)
let first = ExecApprovalsStore.resolveResult(agentId: "main")
guard case .failure(.migrationRequired) = first else {
Issue.record("expected typed migration-required failure")
return
}
let second = ExecApprovalsStore.resolveResult(agentId: "main")
guard case .failure(.migrationRequired) = second else {
Issue.record("expected cached migration-required failure")
return
}
try FileManager.default.removeItem(at: legacyFileURL)
let recovered = try ExecApprovalsStore.resolveResult(agentId: "main").get()
#expect(recovered.agent.security == .full)
#expect(recovered.agent.ask == .off)
}
}
@Test
func `effective home owns the default approvals database and socket paths`() async throws {
let root = self.realTemporaryDirectory
.appendingPathComponent("openclaw-effective-home-\(UUID().uuidString)", isDirectory: true)
let home = root.appendingPathComponent("home", isDirectory: true)
let stateDir = home.appendingPathComponent(".openclaw", isDirectory: true)
defer { try? FileManager().removeItem(at: root) }
try Self.seedCurrentApprovalsFile(in: stateDir)
try await TestIsolation.withEnvValues([
"OPENCLAW_HOME": home.path,
"OPENCLAW_STATE_DIR": nil,
]) {
#expect(ExecApprovalsStore.databaseURL().path == stateDir
.appendingPathComponent("state/openclaw.sqlite").path)
#expect(ExecApprovalsStore.socketPath() == stateDir.appendingPathComponent(
"exec-approvals.sock").path)
let resolved = try ExecApprovalsStore.resolveResult(agentId: "main").get()
#expect(resolved.agent.security == .full)
#expect(resolved.agent.ask == .off)
}
}
@Test
func `malformed SQLite document fails closed and rejects mutation`() async throws {
try await self.withTempStateDir { _ in
try Self.replaceRawJSON("{")
let resolved = ExecApprovalsStore.resolve(agentId: "main")
#expect(resolved.agent.security == .deny)
#expect(resolved.agent.ask == .off)
#expect(resolved.agent.askFallback == .deny)
let result = ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.security = .full
}
guard case .failure(.unavailable) = result else {
Issue.record("expected malformed-file mutation failure")
return
}
let snapshot = ExecApprovalsStore.readSnapshot()
#expect(snapshot.file.defaults?.security == .deny)
#expect(snapshot.file.defaults?.ask == .off)
}
}
@Test
func `explicit null policy structures fail closed and reject mutation`() async throws {
try await self.withTempStateDir { _ in
for json in [
#"{"version":1,"defaults":null}"#,
#"{"version":1,"defaults":{"security":null}}"#,
#"{"version":1,"agents":null}"#,
#"{"version":1,"agents":{"main":{"allowlist":null}}}"#,
#"{"version":1,"agents":{"main":{"allowlist":[{"pattern":null}]}}}"#,
#"{"version":1,"agents":{"main":{"allowlist":[{"pattern":""}]}}}"#,
#"{"version":1,"agents":{"main":{"allowlist":[{"pattern":" "}]}}}"#,
#"{"version":1,"agents":{"main":{"allowlist":[{"pattern":"/usr/bin/foo","argPattern":null}]}}}"#,
#"{"version":1,"agents":{"main":{"allowlist":[{"pattern":"/usr/bin/foo","argPattern":0}]}}}"#,
#"{"version":1,"agents":{"main":{"allowlist":[{"pattern":"/usr/bin/foo","argPattern":false}]}}}"#,
] {
try Self.replaceRawJSON(json)
let resolved = ExecApprovalsStore.resolve(agentId: "main")
#expect(resolved.agent.security == .deny)
#expect(resolved.agent.ask == .off)
let result = ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.security = .full
}
guard case .failure(.unavailable) = result else {
Issue.record("expected invalid-structure mutation failure")
return
}
}
}
}
@Test
func `string source and arg pattern bytes remain cross-runtime compatible`() async throws {
try await self.withTempStateDir { _ in
try Self.replaceRawJSON(
"""
{
"version": 1,
"agents": {
"main": {
"security": "allowlist",
"ask": "off",
"allowlist": [{
"id": "external-entry",
"pattern": "/usr/bin/printf",
"source": "external-policy",
"argPattern": " "
}]
}
}
}
""")
let resolved = try ExecApprovalsStore.resolveResult(agentId: "main").get()
let entry = try #require(resolved.allowlist.first)
#expect(resolved.agent.security == .allowlist)
#expect(entry.id == "external-entry")
#expect(entry.pattern == "/usr/bin/printf")
#expect(entry.source == "external-policy")
#expect(entry.argPattern == " ")
let persisted = try #require(
ExecApprovalsStore.loadFile().agents?["main"]?.allowlist?.first)
#expect(persisted.source == "external-policy")
#expect(persisted.argPattern == " ")
}
}
@Test
func `native add preserves arg pattern bytes`() async throws {
try await self.withTempStateDir { _ in
_ = try ExecApprovalsStore.addAllowlistEntry(
agentId: "main",
pattern: "/usr/bin/rg",
argPattern: " ^safe$ ").get()
let entry = try #require(
ExecApprovalsStore.loadFile().agents?["main"]?.allowlist?.first)
#expect(entry.argPattern == " ^safe$ ")
}
}
@Test
func `missing and present empty snapshots have distinct hashes`() async throws {
try await self.withTempStateDir(seedCurrentApprovals: false) { _ in
let missing = ExecApprovalsStore.readSnapshot()
#expect(!missing.exists)
#expect(missing.hash.hasPrefix("missing:"))
_ = ExecApprovalsStore.ensureFile()
let empty = ExecApprovalsStore.readSnapshot()
#expect(empty.exists)
#expect(!empty.hash.hasPrefix("missing:"))
#expect(empty.hash != missing.hash)
}
}
@Test
func `native rewrites preserve non-sensitive metadata and arbitrary ids`() async throws {
try await self.withTempStateDir { _ in
try Self.replaceRawJSON(
"""
{
"version": 1,
"agents": {
"main": {
"allowlist": [{
"id": "ts:approval/id",
"pattern": "/usr/bin/python3",
"source": "allow-always",
"commandText": "python3 safe.py",
"argPattern": "^safe\\\\.py$"
}]
}
}
}
""")
_ = ExecApprovalsStore.ensureFile()
let entry = try #require(ExecApprovalsStore.loadFile().agents?["main"]?.allowlist?.first)
#expect(entry.id == "ts:approval/id")
#expect(entry.pattern == "/usr/bin/python3")
#expect(entry.source == "allow-always")
#expect(entry.commandText == nil)
#expect(entry.argPattern == #"^safe\.py$"#)
_ = try ExecApprovalsStore.addAllowlistEntry(
agentId: "main",
pattern: "/bin/echo",
commandText: "echo secret-token").get()
let entries = try #require(ExecApprovalsStore.loadFile().agents?["main"]?.allowlist)
#expect(entries.allSatisfy { $0.commandText == nil })
let record = try ExecApprovalsSQLiteStore.read(
stateDirectoryURL: ExecApprovalsStore.databaseURL()
.deletingLastPathComponent()
.deletingLastPathComponent())
let persisted = try #require(record)
#expect(!persisted.rawJSON.contains("commandText"))
}
}
@Test
func `usage updates preserve metadata and select matching arg pattern`() async throws {
try await self.withTempStateDir { _ in
let first = ExecAllowlistEntry(
id: "first",
pattern: "/usr/bin/python3",
source: "allow-always",
commandText: "python3 a.py",
argPattern: #"^a\.py$"#)
let second = ExecAllowlistEntry(
id: "second",
pattern: "/usr/bin/python3",
source: "allow-always",
commandText: "python3 b.py",
argPattern: #"^b\.py$"#)
_ = try ExecApprovalsStore.addAllowlistEntries(
agentId: "main",
entries: [first, second]).get()
_ = try ExecApprovalsStore.recordAllowlistUses(
agentId: "main",
uses: [ExecAllowlistUse(match: first, resolvedPath: "/usr/bin/python3")],
command: "python3 a.py").get()
let entries = try #require(ExecApprovalsStore.loadFile().agents?["main"]?.allowlist)
#expect(entries[0].lastUsedCommand == "python3 a.py")
#expect(entries[0].source == "allow-always")
#expect(entries[0].commandText == nil)
#expect(entries[0].argPattern == #"^a\.py$"#)
#expect(entries[1].lastUsedCommand == nil)
}
}
@Test
func `usage updates omit command text for generated hashed arg patterns`() async throws {
try await self.withTempStateDir { _ in
let hashed = ExecAllowlistEntry(
id: "hashed",
pattern: "/usr/bin/curl",
source: "allow-always",
argPattern: "sha256:argv:test-digest")
_ = try ExecApprovalsStore.addAllowlistEntries(agentId: "main", entries: [hashed]).get()
_ = try ExecApprovalsStore.recordAllowlistUses(
agentId: "main",
uses: [ExecAllowlistUse(match: hashed, resolvedPath: "/usr/bin/curl")],
command: "curl https://trusted.example/install.sh?token=secret").get()
let entry = try #require(ExecApprovalsStore.loadFile().agents?["main"]?.allowlist?.first)
#expect(entry.pattern == "/usr/bin/curl")
#expect(entry.argPattern == "sha256:argv:test-digest")
#expect(entry.lastUsedAt != nil)
#expect(entry.lastResolvedPath == "/usr/bin/curl")
#expect(entry.lastUsedCommand == nil)
}
}
@Test
func `usage checkpoint rejects a revoked reusable approval`() async throws {
try await self.withTempStateDir { _ in
let stale = ExecAllowlistEntry(id: "stale", pattern: "/usr/bin/printf")
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.security = .allowlist
entry.ask = .off
entry.allowlist = [stale]
}.get()
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.allowlist = []
}.get()
let result = ExecApprovalsStore.recordAllowlistUses(
agentId: "main",
uses: [ExecAllowlistUse(match: stale, resolvedPath: "/usr/bin/printf")],
command: "printf ok",
authorization: .currentPolicy(
evaluatedSecurity: .allowlist,
evaluatedAsk: .off,
basis: .allowlistEntries))
guard case .failure(.unavailable) = result else {
Issue.record("expected revoked approval checkpoint to fail")
return
}
let allowlist = ExecApprovalsStore.loadFile().agents?["main"]?.allowlist
#expect(allowlist?.isEmpty ?? true)
}
}
@Test
func `usage checkpoint rejects changed arg pattern bytes`() async throws {
try await self.withTempStateDir { _ in
let stale = ExecAllowlistEntry(
id: "stale",
pattern: "/usr/bin/rg",
argPattern: "^safe$")
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.security = .allowlist
entry.ask = .off
entry.allowlist = [stale]
}.get()
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.allowlist = [ExecAllowlistEntry(
id: "stale",
pattern: "/usr/bin/rg",
argPattern: " ^safe$ ")]
}.get()
let result = ExecApprovalsStore.commitExecution(ExecApprovalExecutionCommit(
agentId: "main",
command: "rg safe",
authorization: .currentPolicy(
evaluatedSecurity: .allowlist,
evaluatedAsk: .off,
basis: .allowlistEntries),
uses: [ExecAllowlistUse(match: stale, resolvedPath: "/usr/bin/rg")]))
guard case .failure(.unavailable) = result else {
Issue.record("expected changed arg pattern approval checkpoint to fail")
return
}
let current = try #require(
ExecApprovalsStore.loadFile().agents?["main"]?.allowlist?.first)
#expect(current.argPattern == " ^safe$ ")
#expect(current.lastUsedCommand == nil)
}
}
@Test
func `usage checkpoint rejects canonically equivalent changed arg pattern bytes`() async throws {
try await self.withTempStateDir { _ in
let evaluatedArgPattern = "^caf\u{00E9}$"
let currentArgPattern = "^cafe\u{0301}$"
#expect(evaluatedArgPattern == currentArgPattern)
#expect(Data(evaluatedArgPattern.utf8) != Data(currentArgPattern.utf8))
let stale = ExecAllowlistEntry(
id: "stale",
pattern: "/usr/bin/rg",
argPattern: evaluatedArgPattern)
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.security = .allowlist
entry.ask = .off
entry.allowlist = [stale]
}.get()
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.allowlist = [ExecAllowlistEntry(
id: "stale",
pattern: "/usr/bin/rg",
argPattern: currentArgPattern)]
}.get()
let result = ExecApprovalsStore.commitExecution(ExecApprovalExecutionCommit(
agentId: "main",
command: "rg caf\u{00E9}",
authorization: .currentPolicy(
evaluatedSecurity: .allowlist,
evaluatedAsk: .off,
basis: .allowlistEntries),
uses: [ExecAllowlistUse(match: stale, resolvedPath: "/usr/bin/rg")]))
guard case .failure(.unavailable) = result else {
Issue.record("expected Unicode-normalized approval checkpoint to fail")
return
}
let current = try #require(
ExecApprovalsStore.loadFile().agents?["main"]?.allowlist?.first)
#expect(try Data(#require(current.argPattern).utf8) == Data(currentArgPattern.utf8))
#expect(current.lastUsedCommand == nil)
}
}
@Test
func `allowlist match key separates embedded nul boundaries`() {
let nulInPattern = ExecAllowlistEntry(
pattern: "/usr/bin/rg\0safe",
argPattern: "value")
let nulInArgPattern = ExecAllowlistEntry(
pattern: "/usr/bin/rg",
argPattern: "safe\0value")
#expect(
ExecApprovalsStore.allowlistEntryMatchKey(nulInPattern) !=
ExecApprovalsStore.allowlistEntryMatchKey(nulInArgPattern))
}
@Test
func `execution commit rejects unprompted full policy after concurrent deny`() async throws {
try await self.withTempStateDir { _ in
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.security = .deny
entry.ask = .off
}.get()
let result = ExecApprovalsStore.commitExecution(ExecApprovalExecutionCommit(
agentId: "main",
command: "printf ok",
authorization: .currentPolicy(
evaluatedSecurity: .full,
evaluatedAsk: .off,
basis: nil),
uses: []))
guard case .failure(.unavailable) = result else {
Issue.record("expected stale full policy authorization to fail")
return
}
}
}
@Test
func `execution commit rejects ask tightening from off to on miss`() async throws {
try await self.withTempStateDir { _ in
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.security = .full
entry.ask = .onMiss
}.get()
let result = ExecApprovalsStore.commitExecution(ExecApprovalExecutionCommit(
agentId: "main",
command: "printf ok",
authorization: .currentPolicy(
evaluatedSecurity: .full,
evaluatedAsk: .off,
basis: nil),
uses: []))
guard case .failure(.unavailable) = result else {
Issue.record("expected stricter ask policy to reject stale authorization")
return
}
}
}
@Test
func `execution commit rejects explicit approval after concurrent deny`() async throws {
try await self.withTempStateDir { _ in
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.security = .full
entry.ask = .off
}.get()
let policySnapshot = ExecApprovalPolicySnapshot(
resolved: ExecApprovalsStore.resolve(agentId: "main"))
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.security = .deny
entry.ask = .off
}.get()
let result = ExecApprovalsStore.commitExecution(ExecApprovalExecutionCommit(
agentId: "main",
command: "printf ok",
authorization: .explicitOnce(
evaluatedSecurity: .full,
policySnapshot: policySnapshot),
uses: []))
guard case .failure(.unavailable) = result else {
Issue.record("expected stale explicit authorization to fail")
return
}
}
}
@Test
func `execution commit rejects auto review after ask changes to always`() async throws {
try await self.withTempStateDir { _ in
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.security = .full
entry.ask = .onMiss
}.get()
let policySnapshot = ExecApprovalPolicySnapshot(
resolved: ExecApprovalsStore.resolve(agentId: "main"))
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.ask = .always
}.get()
let result = ExecApprovalsStore.commitExecution(ExecApprovalExecutionCommit(
agentId: "main",
command: "printf ok",
authorization: .autoReview(
evaluatedSecurity: .full,
policySnapshot: policySnapshot),
uses: []))
guard case .failure(.unavailable) = result else {
Issue.record("expected stale auto-review authorization to fail")
return
}
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.security = .deny
entry.ask = .off
}.get()
let denied = ExecApprovalsStore.commitExecution(ExecApprovalExecutionCommit(
agentId: "main",
command: "printf ok",
authorization: .autoReview(
evaluatedSecurity: .full,
policySnapshot: policySnapshot),
uses: []))
guard case .failure(.unavailable) = denied else {
Issue.record("expected deny policy to override auto review")
return
}
}
}
@Test
func `forwarded explicit approval cannot restore a rule revoked before Mac evaluation`() async throws {
try await self.withTempStateDir { _ in
let stale = ExecAllowlistEntry(
pattern: "/usr/bin/printf",
source: "allow-always")
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.security = .allowlist
entry.ask = .always
entry.allowlist = [stale]
}.get()
let forwardedSnapshot = ExecApprovalPolicySnapshot(
resolved: ExecApprovalsStore.resolve(agentId: "main"))
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.allowlist = []
}.get()
let freshContext = await ExecApprovalEvaluator.evaluate(
command: ["/usr/bin/printf", "ok"],
rawCommand: nil,
cwd: nil,
envOverrides: nil,
agentId: "main")
#expect(freshContext.policySnapshot != forwardedSnapshot)
let commit = ExecApprovalExecutionCommit.build(
context: freshContext,
effectiveSecurity: .allowlist,
approvalSource: nil,
explicitlyApproved: true,
persistAllowlist: true,
delayedPolicySnapshot: forwardedSnapshot)
if case let .explicitAlways(_, policySnapshot, grants) = commit.authorization {
#expect(policySnapshot == forwardedSnapshot)
#expect(grants.map(\.match.pattern) == ["/usr/bin/printf"])
} else {
Issue.record("expected forwarded durable approval")
}
let result = ExecApprovalsStore.commitExecution(commit)
guard case .failure(.unavailable) = result else {
Issue.record("expected revoked forwarded approval to fail")
return
}
#expect(ExecApprovalsStore.resolve(agentId: "main").allowlist.isEmpty)
}
}
@Test
func `execution commit cannot restore a revoked allow always rule`() async throws {
try await self.withTempStateDir { _ in
let stale = ExecAllowlistEntry(pattern: "/usr/bin/printf")
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.security = .allowlist
entry.ask = .always
entry.allowlist = [stale]
}.get()
let evaluated = ExecApprovalsStore.resolve(agentId: "main")
let policySnapshot = ExecApprovalPolicySnapshot(
security: evaluated.agent.security,
ask: evaluated.agent.ask,
askFallback: evaluated.agent.askFallback,
autoAllowSkills: evaluated.agent.autoAllowSkills,
allowlist: evaluated.allowlist)
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.allowlist = []
}.get()
let grant = ExecAllowlistUse(
match: ExecAllowlistEntry(pattern: "/usr/bin/printf", source: "allow-always"),
resolvedPath: "/usr/bin/printf")
let result = ExecApprovalsStore.commitExecution(ExecApprovalExecutionCommit(
agentId: "main",
command: "printf ok",
authorization: .explicitAlways(
evaluatedSecurity: .allowlist,
policySnapshot: policySnapshot,
grants: [grant]),
uses: [ExecAllowlistUse(match: stale, resolvedPath: "/usr/bin/printf")]))
guard case .failure(.unavailable) = result else {
Issue.record("expected revoked durable grant commit to fail")
return
}
#expect(ExecApprovalsStore.resolve(agentId: "main").allowlist.isEmpty)
}
}
@Test
func `execution commit atomically persists allow always audit metadata`() async throws {
try await self.withTempStateDir { _ in
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.security = .allowlist
entry.ask = .onMiss
}.get()
let evaluated = ExecApprovalsStore.resolve(agentId: "main")
let policySnapshot = ExecApprovalPolicySnapshot(
security: evaluated.agent.security,
ask: evaluated.agent.ask,
askFallback: evaluated.agent.askFallback,
autoAllowSkills: evaluated.agent.autoAllowSkills,
allowlist: evaluated.allowlist)
let grant = ExecAllowlistUse(
match: ExecAllowlistEntry(
pattern: "/usr/bin/printf",
source: "allow-always",
argPattern: " ^ok$ "),
resolvedPath: "/usr/bin/printf")
_ = try ExecApprovalsStore.commitExecution(ExecApprovalExecutionCommit(
agentId: "main",
command: "printf ok",
authorization: .explicitAlways(
evaluatedSecurity: .allowlist,
policySnapshot: policySnapshot,
grants: [grant]),
uses: [])).get()
let entry = try #require(ExecApprovalsStore.resolve(agentId: "main").allowlist.first)
#expect(entry.pattern == "/usr/bin/printf")
#expect(entry.source == "allow-always")
#expect(entry.argPattern == " ^ok$ ")
#expect(entry.lastUsedAt != nil)
#expect(entry.lastUsedCommand == "printf ok")
#expect(entry.lastResolvedPath == "/usr/bin/printf")
}
}
@Test
func `stale concurrent allow always snapshots preserve additive grants and upgrades`() async throws {
try await self.withTempStateDir { _ in
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "researcher") { entry in
entry.security = .allowlist
entry.ask = .always
entry.allowlist = [ExecAllowlistEntry(pattern: "/usr/bin/grep")]
}.get()
let evaluated = ExecApprovalsStore.resolve(agentId: "researcher")
let policySnapshot = ExecApprovalPolicySnapshot(
security: evaluated.agent.security,
ask: evaluated.agent.ask,
askFallback: evaluated.agent.askFallback,
autoAllowSkills: evaluated.agent.autoAllowSkills,
allowlist: evaluated.allowlist)
let commitGrant: (String) throws -> Void = { pattern in
let grant = ExecAllowlistUse(
match: ExecAllowlistEntry(pattern: pattern, source: "allow-always"),
resolvedPath: pattern)
_ = try ExecApprovalsStore.commitExecution(ExecApprovalExecutionCommit(
agentId: "researcher",
command: "\(pattern) --version",
authorization: .explicitAlways(
evaluatedSecurity: .allowlist,
policySnapshot: policySnapshot,
grants: [grant]),
uses: [])).get()
}
try commitGrant("/usr/bin/grep")
try commitGrant("/usr/bin/cat")
let allowlist = ExecApprovalsStore.resolve(agentId: "researcher").allowlist
#expect(Set(allowlist.map(\.pattern)) == ["/usr/bin/grep", "/usr/bin/cat"])
#expect(allowlist.allSatisfy { $0.source == "allow-always" })
}
}
@Test
func `usage checkpoint rejects current deny policy`() async throws {
try await self.withTempStateDir { _ in
let stale = ExecAllowlistEntry(id: "stale", pattern: "/usr/bin/printf")
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.security = .deny
entry.ask = .off
entry.allowlist = [stale]
}.get()
let result = ExecApprovalsStore.recordAllowlistUses(
agentId: "main",
uses: [ExecAllowlistUse(match: stale, resolvedPath: "/usr/bin/printf")],
command: "printf ok",
authorization: .currentPolicy(
evaluatedSecurity: .allowlist,
evaluatedAsk: .off,
basis: .allowlistEntries))
guard case .failure(.unavailable) = result else {
Issue.record("expected deny policy checkpoint to fail")
return
}
let entry = try #require(ExecApprovalsStore.loadFile().agents?["main"]?.allowlist?.first)
#expect(entry.lastUsedAt == nil)
}
}
@Test
func `usage checkpoint rejects skill trust after auto allow is removed`() async throws {
try await self.withTempStateDir { _ in
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.security = .allowlist
entry.ask = .off
entry.autoAllowSkills = true
}.get()
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.autoAllowSkills = nil
}.get()
let result = ExecApprovalsStore.recordAllowlistUses(
agentId: "main",
uses: [],
command: "skill-tool",
authorization: .currentPolicy(
evaluatedSecurity: .allowlist,
evaluatedAsk: .off,
basis: .autoAllowedSkill))
guard case .failure(.unavailable) = result else {
Issue.record("expected revoked skill trust checkpoint to fail")
return
}
}
}
@Test
func `usage checkpoint applies current timeout fallback instead of ask`() async throws {
try await self.withTempStateDir { _ in
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.security = .full
entry.ask = .always
entry.askFallback = .full
}.get()
let result = ExecApprovalsStore.recordAllowlistUses(
agentId: "main",
uses: [],
command: "printf fallback",
authorization: .askFallback(
evaluatedSecurity: .full,
basis: nil))
_ = try result.get()
}
}
@Test
func `usage checkpoint rejects a revoked timeout fallback`() async throws {
try await self.withTempStateDir { _ in
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.security = .full
entry.ask = .always
entry.askFallback = .deny
}.get()
let result = ExecApprovalsStore.recordAllowlistUses(
agentId: "main",
uses: [],
command: "printf fallback",
authorization: .askFallback(
evaluatedSecurity: .full,
basis: nil))
guard case .failure(.unavailable) = result else {
Issue.record("expected revoked timeout fallback checkpoint to fail")
return
}
}
}
@Test
func `usage checkpoint rejects fallback mode tightening`() async throws {
try await self.withTempStateDir { _ in
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.security = .full
entry.ask = .always
entry.askFallback = .allowlist
entry.allowlist = [ExecAllowlistEntry(pattern: "/usr/bin/printf")]
}.get()
let result = ExecApprovalsStore.recordAllowlistUses(
agentId: "main",
uses: [],
command: "printf fallback",
authorization: .askFallback(
evaluatedSecurity: .full,
basis: nil))
guard case .failure(.unavailable) = result else {
Issue.record("expected tightened timeout fallback checkpoint to fail")
return
}
}
}
}
extension ExecApprovalsStoreRefactorTests {
@Test
func `add allowlist entries accepts basename pattern`() async throws {
try await self.withTempStateDir { _ in
_ = try ExecApprovalsStore.addAllowlistEntries(
agentId: "main",
entries: [
ExecAllowlistEntry(pattern: "echo"),
ExecAllowlistEntry(pattern: "/bin/echo"),
]).get()
let resolved = ExecApprovalsStore.resolve(agentId: "main")
#expect(resolved.allowlist.map(\.pattern) == ["echo", "/bin/echo"])
}
}
@Test
func `ensure file migrates legacy pattern from resolved path`() async throws {
try await self.withTempStateDir { _ in
try Self.replaceRawJSON(
#"{"version":1,"agents":{"main":{"allowlist":[{"pattern":"echo","lastResolvedPath":" /usr/bin/echo "}]}}}"#)
let ensured = ExecApprovalsStore.ensureFile()
#expect(ensured.agents?["main"]?.allowlist?.map(\.pattern) == ["/usr/bin/echo"])
#expect(ExecApprovalsStore.resolve(agentId: "main").allowlist.map(\.pattern) == ["/usr/bin/echo"])
}
}
@Test
func `entry scoped update persists a legacy missing id before editing`() async throws {
try await self.withTempStateDir { _ in
try Self.replaceRawJSON(
"""
{"version":1,"agents":{"main":{"allowlist":[{"pattern":"/bin/echo"}]}}}
""")
let resolved = ExecApprovalsStore.resolve(agentId: "main")
guard let id = resolved.allowlist.first?.id else {
Issue.record("expected legacy allowlist entry")
return
}
let result = ExecApprovalsStore.updateAllowlistEntry(
agentId: "main",
id: id,
pattern: "/bin/cat")
if case let .failure(error) = result {
Issue.record("unexpected update failure: \(error)")
}
let persisted = ExecApprovalsStore.loadFile().agents?["main"]?.allowlist
#expect(persisted?.map(\.id) == [id])
#expect(persisted?.map(\.pattern) == ["/bin/cat"])
}
}
@Test
func `legacy string entry receives a stable persisted id`() async throws {
try await self.withTempStateDir { _ in
try Self.replaceRawJSON(
#"{"version":1,"agents":{"main":{"allowlist":["/bin/echo"]}}}"#)
let first = try #require(ExecApprovalsStore.ensureFile().agents?["main"]?.allowlist?.first)
let second = try #require(ExecApprovalsStore.loadFile().agents?["main"]?.allowlist?.first)
#expect(first.id == second.id)
#expect(first.pattern == "/bin/echo")
let record = try ExecApprovalsSQLiteStore.read(
stateDirectoryURL: ExecApprovalsStore.databaseURL()
.deletingLastPathComponent()
.deletingLastPathComponent())
let persisted = try #require(record)
#expect(persisted.rawJSON.contains(first.id))
#expect(!persisted.rawJSON.contains(#"["/bin/echo"]"#))
}
}
@Test
func `entry scoped update cannot restore a revoked allowlist snapshot`() async throws {
try await self.withTempStateDir { _ in
_ = try ExecApprovalsStore.addAllowlistEntry(
agentId: "main",
pattern: "/bin/echo").get()
let revokedID = try #require(ExecApprovalsStore.resolve(agentId: "main").allowlist.first?.id)
_ = try ExecApprovalsStore.removeAllowlistEntry(
agentId: "main",
id: revokedID).get()
_ = try ExecApprovalsStore.addAllowlistEntry(
agentId: "main",
pattern: "/usr/bin/date").get()
let result = ExecApprovalsStore.updateAllowlistEntry(
agentId: "main",
id: revokedID,
pattern: "/bin/cat")
if case let .failure(error) = result {
Issue.record("unexpected update failure: \(error)")
}
#expect(ExecApprovalsStore.resolve(agentId: "main").allowlist.map(\.pattern) == ["/usr/bin/date"])
}
}
@Test
func `entry scoped mutations reject inherited wildcard entries`() async throws {
try await self.withTempStateDir { _ in
let inherited = ExecAllowlistEntry(id: "wildcard-entry", pattern: "/bin/echo")
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "*") { entry in
entry.allowlist = [inherited]
}.get()
#expect(ExecApprovalsStore.resolve(agentId: "main").allowlist.map(\.id) == [inherited.id])
let update = ExecApprovalsStore.updateAllowlistEntry(
agentId: "main",
id: inherited.id,
pattern: "/bin/cat")
let removal = ExecApprovalsStore.removeAllowlistEntry(
agentId: "main",
id: inherited.id)
if case .failure(.entryNotOwned) = update {} else {
Issue.record("expected inherited update to report entryNotOwned")
}
if case .failure(.entryNotOwned) = removal {} else {
Issue.record("expected inherited removal to report entryNotOwned")
}
let persisted = ExecApprovalsStore.loadFile().agents?["*"]?.allowlist
#expect(persisted?.map(\.id) == [inherited.id])
#expect(persisted?.map(\.pattern) == [inherited.pattern])
}
}
@Test
func `conditional save cannot restore revoked approvals`() async throws {
try await self.withTempStateDir { _ in
ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.security = .allowlist
entry.allowlist = [ExecAllowlistEntry(pattern: "/bin/echo")]
}
let stale = ExecApprovalsStore.readSnapshot()
ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.security = .deny
entry.allowlist = []
}
let result = ExecApprovalsStore.saveFile(stale.file, ifBaseHash: stale.hash)
if case .conflict = result {
// Expected: the revocation changed the hash before the stale save.
} else {
Issue.record("expected stale conditional save to conflict")
}
let current = ExecApprovalsStore.resolve(agentId: "main")
#expect(current.agent.security == .deny)
#expect(current.allowlist.isEmpty)
}
}
@Test
func `conditional save does not recreate a deleted approval row`() async throws {
try await self.withTempStateDir { _ in
_ = try ExecApprovalsStore.updateAgentSettings(agentId: "main") { entry in
entry.security = .allowlist
entry.allowlist = [ExecAllowlistEntry(pattern: "/bin/echo")]
}.get()
let stale = ExecApprovalsStore.readSnapshot()
let databaseURL = ExecApprovalsStore.databaseURL()
try FileManager().removeItem(at: databaseURL)
let result = ExecApprovalsStore.saveFile(stale.file, ifBaseHash: stale.hash)
switch result {
case .conflict, .baseHashUnavailable:
break
default:
Issue.record("expected deleted approval state to remain absent")
}
let stateDirectoryURL = databaseURL
.deletingLastPathComponent()
.deletingLastPathComponent()
#expect(try ExecApprovalsSQLiteStore.read(
stateDirectoryURL: stateDirectoryURL) == nil)
}
}
private static func seedCurrentApprovalsFile(in stateDir: URL) throws {
let file = ExecApprovalsFile(
version: 1,
socket: ExecApprovalsSocketConfig(
path: stateDir.appendingPathComponent("exec-approvals.sock").path,
token: "test-token"),
defaults: nil,
agents: [:])
try ExecApprovalsSQLiteStore.write(file, stateDirectoryURL: stateDir)
}
private static func replaceRawJSON(_ rawJSON: String) throws {
let databaseURL = ExecApprovalsStore.databaseURL()
var database: OpaquePointer?
guard sqlite3_open(databaseURL.path, &database) == SQLITE_OK, let database else {
throw SQLiteTestError.open
}
defer { sqlite3_close(database) }
var statement: OpaquePointer?
guard sqlite3_prepare_v2(
database,
"UPDATE exec_approvals_config SET raw_json = ? WHERE config_key = 'current'",
-1,
&statement,
nil) == SQLITE_OK,
let statement
else {
throw SQLiteTestError.prepare
}
defer { sqlite3_finalize(statement) }
let transient = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
guard sqlite3_bind_text(statement, 1, rawJSON, -1, transient) == SQLITE_OK,
sqlite3_step(statement) == SQLITE_DONE
else {
throw SQLiteTestError.update
}
}
private enum SQLiteTestError: Error {
case open
case prepare
case update
}
}