From cd64a17e37fbd7acb0bfe447c34046e00332e2a7 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 6 Aug 2026 17:25:00 -0400 Subject: [PATCH] test(core): cover config precedence --- packages/core/test/config/agent.test.ts | 91 ++++++++++++++++++++++++ packages/core/test/config/config.test.ts | 18 ++++- 2 files changed, 106 insertions(+), 3 deletions(-) diff --git a/packages/core/test/config/agent.test.ts b/packages/core/test/config/agent.test.ts index 0060d73bd0e..49aed818489 100644 --- a/packages/core/test/config/agent.test.ts +++ b/packages/core/test/config/agent.test.ts @@ -12,6 +12,7 @@ import { LayerNode } from "@opencode-ai/util/effect/layer-node" import { FSUtil } from "@opencode-ai/util/fs-util" import { Global } from "@opencode-ai/util/global" import { Permission } from "@opencode-ai/core/permission" +import { AgentPlugin } from "@opencode-ai/core/plugin/agent" import { AbsolutePath } from "@opencode-ai/core/schema" import { ConfigMigrateV1 } from "@opencode-ai/core/v1/config/migrate" import { advance, drain } from "../lib/clock" @@ -59,6 +60,96 @@ describe("ConfigAgentPlugin.Plugin", () => { }), ) + it.effect("applies remote permission defaults before explicit global and build rules", () => + Effect.gen(function* () { + const agents = yield* Agent.Service + const global = yield* Global.Service + yield* AgentPlugin.Plugin.effect(host({ agent: agentHost(agents) })) + + const entries = [ + new Document({ + type: "document", + info: decode( + ConfigMigrateV1.migrate({ + permission: { + bash: "ask", + edit: "ask", + webfetch: "ask", + read: { + "*": "allow", + "*.env": "deny", + "*.env.*": "deny", + "*.env.example": "allow", + "*.dev.vars": "deny", + "~/.local/share/opencode/mcp-auth.json": "deny", + "$HOME/.local/share/opencode/mcp-auth.json": "deny", + }, + external_directory: { + "*": "ask", + "~/.local/share/opencode/*": "deny", + }, + }, + }), + ), + }), + new Document({ + type: "document", + info: decode({ + permissions: [{ action: "*", resource: "*", effect: "allow" }], + agents: { + build: { + permissions: [ + { action: "external_directory", resource: "*", effect: "allow" }, + { + action: "external_directory", + resource: "~/.local/share/opencode/*", + effect: "deny", + }, + { action: "read", resource: "*.env", effect: "deny" }, + ], + }, + }, + }), + }), + ] + + yield* ConfigAgentPlugin.Plugin.effect(host({ agent: agentHost(agents) })).pipe( + Effect.provide(Config.testLayer(entries)), + ) + + const build = yield* agents.get(Agent.defaultID) + if (!build) throw new Error("expected configured build agent") + const opencodeData = path.join(global.home, ".local", "share", "opencode", "*") + const mcpAuth = path.join(global.home, ".local", "share", "opencode", "mcp-auth.json") + expect(build.permissions).toEqual([ + ...defaultPermissions(global), + { action: "question", resource: "*", effect: "allow" }, + { action: "shell", resource: "*", effect: "ask" }, + { action: "edit", resource: "*", effect: "ask" }, + { action: "webfetch", resource: "*", effect: "ask" }, + { action: "read", resource: "*", effect: "allow" }, + { action: "read", resource: "*.env", effect: "deny" }, + { action: "read", resource: "*.env.*", effect: "deny" }, + { action: "read", resource: "*.env.example", effect: "allow" }, + { action: "read", resource: "*.dev.vars", effect: "deny" }, + { action: "read", resource: mcpAuth, effect: "deny" }, + { action: "read", resource: mcpAuth, effect: "deny" }, + { action: "external_directory", resource: "*", effect: "ask" }, + { action: "external_directory", resource: opencodeData, effect: "deny" }, + { action: "*", resource: "*", effect: "allow" }, + { action: "external_directory", resource: "*", effect: "allow" }, + { action: "external_directory", resource: opencodeData, effect: "deny" }, + { action: "read", resource: "*.env", effect: "deny" }, + ]) + expect(Permission.evaluate("shell", "bun test", build.permissions).effect).toBe("allow") + expect(Permission.evaluate("edit", "src/index.ts", build.permissions).effect).toBe("allow") + expect(Permission.evaluate("webfetch", "https://example.com", build.permissions).effect).toBe("allow") + expect(Permission.evaluate("read", ".env", build.permissions).effect).toBe("deny") + expect(Permission.evaluate("external_directory", opencodeData, build.permissions).effect).toBe("deny") + expect(Permission.evaluate("external_directory", "/outside/*", build.permissions).effect).toBe("allow") + }), + ) + it.effect("applies all global permissions before agent-specific permissions", () => Effect.gen(function* () { const agents = yield* Agent.Service diff --git a/packages/core/test/config/config.test.ts b/packages/core/test/config/config.test.ts index d7982085281..1d72dfe18f5 100644 --- a/packages/core/test/config/config.test.ts +++ b/packages/core/test/config/config.test.ts @@ -307,7 +307,7 @@ describe("Config", () => { }), ) - it.live("loads authenticated wellknown config below project config", () => + it.live("loads authenticated wellknown config before user configuration", () => Effect.acquireUseRelease( Effect.promise(() => tmpdir()), (tmp) => @@ -370,7 +370,13 @@ describe("Config", () => { return yield* Effect.gen(function* () { const config = yield* Config.Service const bus = yield* Bus.Service - expect(Config.latest(yield* config.entries(), "shell")).toBe("project") + const initial = yield* config.entries() + expect(Config.latest(initial, "shell")).toBe("project") + expect( + initial.flatMap((entry) => + entry.type === "document" && entry.info.shell ? [entry.info.shell] : [], + ), + ).toEqual(["secret", "global", "project"]) const updated = yield* bus .subscribe(Event.Updated) .pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped) @@ -378,7 +384,13 @@ describe("Config", () => { key = "next" yield* bus.publish(Integration.Event.ConnectionUpdated, { integrationID }) expect(yield* Fiber.join(updated)).toHaveLength(1) - expect(Config.latest(yield* config.entries(), "shell")).toBe("project") + const refreshed = yield* config.entries() + expect(Config.latest(refreshed, "shell")).toBe("project") + expect( + refreshed.flatMap((entry) => + entry.type === "document" && entry.info.shell ? [entry.info.shell] : [], + ), + ).toEqual(["next", "global", "project"]) }).pipe( Effect.provide(testLayer(project, global, project, undefined, undefined, credentialNode, wellknownNode)), )