import { describe, test, expect } from "bun:test"
import { Effect } from "effect"
import { Permission } from "../src/permission"
import { Config } from "@/config/config"
import { testEffect } from "./lib/effect"
const it = testEffect(Config.defaultLayer)
const load = Config.Service.use((svc) => svc.get())
describe("Permission.evaluate for permission.task", () => {
const createRuleset = (rules: Record<string, "allow" | "deny" | "ask">): Permission.Ruleset =>
Object.entries(rules).map(([pattern, action]) => ({
permission: "task",
pattern,
action,
}))
test("returns ask when no match (default)", () => {
expect(Permission.evaluate("task", "code-reviewer", []).action).toBe("ask")
})
test("returns deny for explicit deny", () => {
const ruleset = createRuleset({ "code-reviewer": "deny" })
expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("deny")
})
test("returns allow for explicit allow", () => {
const ruleset = createRuleset({ "code-reviewer": "allow" })
expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("allow")
})
test("returns ask for explicit ask", () => {
const ruleset = createRuleset({ "code-reviewer": "ask" })
expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("ask")
})
test("matches wildcard patterns with deny", () => {
const ruleset = createRuleset({ "orchestrator-*": "deny" })
expect(Permission.evaluate("task", "orchestrator-fast", ruleset).action).toBe("deny")
expect(Permission.evaluate("task", "orchestrator-slow", ruleset).action).toBe("deny")
expect(Permission.evaluate("task", "general", ruleset).action).toBe("ask")
})
test("matches wildcard patterns with allow", () => {
const ruleset = createRuleset({ "orchestrator-*": "allow" })
expect(Permission.evaluate("task", "orchestrator-fast", ruleset).action).toBe("allow")
expect(Permission.evaluate("task", "orchestrator-slow", ruleset).action).toBe("allow")
})
test("matches wildcard patterns with ask", () => {
const ruleset = createRuleset({ "orchestrator-*": "ask" })
expect(Permission.evaluate("task", "orchestrator-fast", ruleset).action).toBe("ask")
const globalRuleset = createRuleset({ "*": "ask" })
expect(Permission.evaluate("task", "code-reviewer", globalRuleset).action).toBe("ask")
})
test("later rules take precedence (last match wins)", () => {
const ruleset = createRuleset({
"orchestrator-*": "deny",
"orchestrator-fast": "allow",
})
expect(Permission.evaluate("task", "orchestrator-fast", ruleset).action).toBe("allow")
expect(Permission.evaluate("task", "orchestrator-slow", ruleset).action).toBe("deny")
})
test("matches global wildcard", () => {
expect(Permission.evaluate("task", "any-agent", createRuleset({ "*": "allow" })).action).toBe("allow")
expect(Permission.evaluate("task", "any-agent", createRuleset({ "*": "deny" })).action).toBe("deny")
expect(Permission.evaluate("task", "any-agent", createRuleset({ "*": "ask" })).action).toBe("ask")
})
})
describe("Permission.disabled for task tool", () => {
const createRuleset = (rules: Record<string, "allow" | "deny" | "ask">): Permission.Ruleset =>
Object.entries(rules).map(([pattern, action]) => ({
permission: "task",
pattern,
action,
}))
test("task tool is disabled when global deny pattern exists (even with specific allows)", () => {
const ruleset = createRuleset({
"orchestrator-*": "allow",
"*": "deny",
})
const disabled = Permission.disabled(["task", "bash", "read"], ruleset)
expect(disabled.has("task")).toBe(true)
})
test("task tool is disabled when global deny pattern exists (even with ask overrides)", () => {
const ruleset = createRuleset({
"orchestrator-*": "ask",
"*": "deny",
})
const disabled = Permission.disabled(["task"], ruleset)
expect(disabled.has("task")).toBe(true)
})
test("task tool is disabled when global deny pattern exists", () => {
const ruleset = createRuleset({ "*": "deny" })
const disabled = Permission.disabled(["task"], ruleset)
expect(disabled.has("task")).toBe(true)
})
test("task tool is NOT disabled when only specific patterns are denied (no wildcard)", () => {
const ruleset = createRuleset({
"orchestrator-*": "deny",
general: "deny",
})
const disabled = Permission.disabled(["task"], ruleset)
expect(disabled.has("task")).toBe(false)
})
test("task tool is enabled when no task rules exist (default ask)", () => {
const disabled = Permission.disabled(["task"], [])
expect(disabled.has("task")).toBe(false)
})
test("task tool is NOT disabled when last wildcard pattern is allow", () => {
const ruleset = createRuleset({
"*": "deny",
"orchestrator-coder": "allow",
})
const disabled = Permission.disabled(["task"], ruleset)
expect(disabled.has("task")).toBe(false)
})
})
describe("permission.task with real config files", () => {
it.instance(
"loads task permissions from deveco.json config",
() =>
Effect.gen(function* () {
const config = yield* load
const ruleset = Permission.fromConfig(config.permission ?? {})
expect(Permission.evaluate("task", "general", ruleset).action).toBe("allow")
expect(Permission.evaluate("task", "orchestrator-fast", ruleset).action).toBe("allow")
expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("deny")
}),
{
git: true,
config: {
permission: {
task: {
"*": "allow",
"code-reviewer": "deny",
},
},
},
},
)
it.instance(
"loads task permissions with wildcard patterns from config",
() =>
Effect.gen(function* () {
const config = yield* load
const ruleset = Permission.fromConfig(config.permission ?? {})
expect(Permission.evaluate("task", "general", ruleset).action).toBe("ask")
expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("ask")
expect(Permission.evaluate("task", "orchestrator-fast", ruleset).action).toBe("deny")
}),
{
git: true,
config: {
permission: {
task: {
"*": "ask",
"orchestrator-*": "deny",
},
},
},
},
)
it.instance(
"evaluate respects task permission from config",
() =>
Effect.gen(function* () {
const config = yield* load
const ruleset = Permission.fromConfig(config.permission ?? {})
expect(Permission.evaluate("task", "general", ruleset).action).toBe("allow")
expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("deny")
expect(Permission.evaluate("task", "unknown-agent", ruleset).action).toBe("ask")
}),
{
git: true,
config: {
permission: {
task: {
general: "allow",
"code-reviewer": "deny",
},
},
},
},
)
it.instance(
"mixed permission config with task and other tools",
() =>
Effect.gen(function* () {
const config = yield* load
const ruleset = Permission.fromConfig(config.permission ?? {})
expect(Permission.evaluate("task", "general", ruleset).action).toBe("allow")
expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("deny")
expect(Permission.evaluate("bash", "*", ruleset).action).toBe("allow")
expect(Permission.evaluate("edit", "*", ruleset).action).toBe("ask")
const disabled = Permission.disabled(["bash", "edit", "task"], ruleset)
expect(disabled.has("bash")).toBe(false)
expect(disabled.has("edit")).toBe(false)
expect(disabled.has("task")).toBe(false)
}),
{
git: true,
config: {
permission: {
bash: "allow",
edit: "ask",
task: {
"*": "deny",
general: "allow",
},
},
},
},
)
it.instance(
"task tool disabled when global deny comes last in config",
() =>
Effect.gen(function* () {
const config = yield* load
const ruleset = Permission.fromConfig(config.permission ?? {})
expect(Permission.evaluate("task", "general", ruleset).action).toBe("deny")
expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("deny")
expect(Permission.evaluate("task", "unknown", ruleset).action).toBe("deny")
const disabled = Permission.disabled(["task"], ruleset)
expect(disabled.has("task")).toBe(true)
}),
{
git: true,
config: {
permission: {
task: {
general: "allow",
"code-reviewer": "allow",
"*": "deny",
},
},
},
},
)
it.instance(
"task tool NOT disabled when specific allow comes last in config",
() =>
Effect.gen(function* () {
const config = yield* load
const ruleset = Permission.fromConfig(config.permission ?? {})
expect(Permission.evaluate("task", "general", ruleset).action).toBe("allow")
expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("deny")
const disabled = Permission.disabled(["task"], ruleset)
expect(disabled.has("task")).toBe(false)
}),
{
git: true,
config: {
permission: {
task: {
"*": "deny",
general: "allow",
},
},
},
},
)
})