feat(core): mark policies experimental

This commit is contained in:
Dax Raad
2026-05-27 22:16:46 -04:00
parent e24b589da1
commit 4cd69df7c8
9 changed files with 146 additions and 110 deletions
+1 -1
View File
@@ -82,7 +82,7 @@ export const layer = Layer.effect(
const configs = [...(supplementary[0] ?? []), ...direct, ...supplementary.slice(1).flat()]
// Rules use the opposite order so a user-global rule can override a
// repository rule. Statement order inside each file stays unchanged.
yield* policy.load(configs.toReversed().flatMap((config) => config.info.policies ?? []))
yield* policy.load(configs.toReversed().flatMap((config) => config.info.experimental?.policies ?? []))
return Service.of({
directories: Effect.fn("Config.directories")(function* () {
+5 -1
View File
@@ -14,6 +14,10 @@ export class Policy extends Schema.Class<Policy>("ConfigV2.Policy")({
action: PolicyAction,
}) {}
export class Experimental extends Schema.Class<Experimental>("ConfigV2.Experimental")({
policies: Policy.pipe(Schema.Array, Schema.optional),
}) {}
export class Info extends Schema.Class<Info>("ConfigV2.Info")({
$schema: Schema.optional(Schema.String).annotate({
description: "JSON schema reference for configuration validation",
@@ -21,7 +25,7 @@ export class Info extends Schema.Class<Info>("ConfigV2.Info")({
shell: Schema.String.pipe(Schema.optional).annotate({
description: "Default shell to use for terminal and shell tool execution",
}),
policies: Policy.pipe(Schema.Array, Schema.optional),
experimental: Experimental.pipe(Schema.optional),
providers: Schema.Record(Schema.String, ConfigProvider.Info).pipe(Schema.optional),
}) {}
+4 -4
View File
@@ -132,7 +132,7 @@ describe("Config", () => {
const file = path.join(tmp.path, "opencode.json")
const contents = JSON.stringify({
shell: "/bin/zsh",
policies: [{ effect: "deny", action: "provider.use", resource: "openai" }],
experimental: { policies: [{ effect: "deny", action: "provider.use", resource: "openai" }] },
providers: { local: provider },
})
yield* Effect.promise(() => fs.writeFile(file, contents))
@@ -143,7 +143,7 @@ describe("Config", () => {
expect(documents[0]?.info.$schema).toBeUndefined()
expect(documents[0]?.info.shell).toBe("/bin/zsh")
expect(documents[0]?.info.policies?.[0]).toEqual({
expect(documents[0]?.info.experimental?.policies?.[0]).toEqual({
effect: "deny",
action: "provider.use",
resource: "openai",
@@ -223,11 +223,11 @@ describe("Config", () => {
await fs.mkdir(global, { recursive: true })
await fs.writeFile(
path.join(global, "opencode.json"),
JSON.stringify({ policies: [{ effect: "deny", action: "provider.use", resource: "openai" }] }),
JSON.stringify({ experimental: { policies: [{ effect: "deny", action: "provider.use", resource: "openai" }] } }),
)
await fs.writeFile(
path.join(tmp.path, "opencode.json"),
JSON.stringify({ policies: [{ effect: "allow", action: "provider.use", resource: "openai" }] }),
JSON.stringify({ experimental: { policies: [{ effect: "allow", action: "provider.use", resource: "openai" }] } }),
)
})
+3 -3
View File
@@ -178,9 +178,6 @@ export const Info = Schema.Struct({
enabled_providers: Schema.optional(Schema.mutable(Schema.Array(Schema.String))).annotate({
description: "When set, ONLY these providers will be enabled. All other providers will be ignored",
}),
policies: Schema.optional(Schema.mutable(Schema.Array(ConfigV2.Policy))).annotate({
description: "Policy statements applied to supported resources, such as provider access",
}),
model: Schema.optional(ConfigModelID).annotate({
description: "Model to use in the format of provider/model, eg anthropic/claude-2",
}),
@@ -305,6 +302,9 @@ export const Info = Schema.Struct({
mcp_timeout: Schema.optional(PositiveInt).annotate({
description: "Timeout in milliseconds for model context protocol (MCP) requests",
}),
policies: Schema.optional(Schema.mutable(Schema.Array(ConfigV2.Policy))).annotate({
description: "Policy statements applied to supported resources, such as provider access",
}),
}),
),
}).annotate({ identifier: "Config" })
@@ -104,7 +104,9 @@ const alphaProviderConfig = {
const denyAnthropicPolicyConfig = {
provider: {},
policies: [{ effect: "deny" as const, action: "provider.use" as const, resource: "anthropic" }],
experimental: {
policies: [{ effect: "deny" as const, action: "provider.use" as const, resource: "anthropic" }],
},
}
it.instance("provider loaded from env variable", () =>
@@ -139,7 +141,7 @@ it.instance(
)
it.instance(
"policies deny provider use",
"experimental policies deny provider use",
Effect.gen(function* () {
yield* setProcessEnv("ANTHROPIC_API_KEY", "test-api-key")
const providers = yield* list
+10 -8
View File
@@ -395,18 +395,20 @@ You can also configure [local models](/docs/models#local). [Learn more](/docs/mo
### Policies
Use the `policies` option to allow or deny OpenCode actions on configured resources. Currently, policies can control which providers OpenCode may use.
Use the `experimental.policies` option to allow or deny OpenCode actions on configured resources. Currently, policies can control which providers OpenCode may use.
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"policies": [
{
"effect": "deny",
"action": "provider.use",
"resource": "openai"
}
]
"experimental": {
"policies": [
{
"effect": "deny",
"action": "provider.use",
"resource": "openai"
}
]
}
}
```
+46 -36
View File
@@ -3,7 +3,7 @@ title: Policies
description: Control which configured resources OpenCode may use.
---
Policies control whether OpenCode may perform an action on a named resource. They are configured with the `policies` array in `opencode.json`.
Policies control whether OpenCode may perform an action on a named resource. This feature is experimental and is configured with the `experimental.policies` array in `opencode.json`.
Policies are separate from [permissions](/docs/permissions). Permissions control what tools can do during a session, while policies control whether OpenCode may use a resource such as an LLM provider.
@@ -22,13 +22,15 @@ For example, deny use of the `openai` provider:
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"policies": [
{
"effect": "deny",
"action": "provider.use",
"resource": "openai"
}
]
"experimental": {
"policies": [
{
"effect": "deny",
"action": "provider.use",
"resource": "openai"
}
]
}
}
```
@@ -55,13 +57,15 @@ The `resource` field supports wildcard matching. Use `*` to match zero or more c
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"policies": [
{
"effect": "deny",
"action": "provider.use",
"resource": "company-*"
}
]
"experimental": {
"policies": [
{
"effect": "deny",
"action": "provider.use",
"resource": "company-*"
}
]
}
}
```
@@ -78,18 +82,20 @@ For example, allow only Anthropic:
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"policies": [
{
"effect": "deny",
"action": "provider.use",
"resource": "*"
},
{
"effect": "allow",
"action": "provider.use",
"resource": "anthropic"
}
]
"experimental": {
"policies": [
{
"effect": "deny",
"action": "provider.use",
"resource": "*"
},
{
"effect": "allow",
"action": "provider.use",
"resource": "anthropic"
}
]
}
}
```
@@ -107,10 +113,12 @@ To replace `disabled_providers`:
```json title="opencode.json"
{
"policies": [
{ "effect": "deny", "action": "provider.use", "resource": "openai" },
{ "effect": "deny", "action": "provider.use", "resource": "google" }
]
"experimental": {
"policies": [
{ "effect": "deny", "action": "provider.use", "resource": "openai" },
{ "effect": "deny", "action": "provider.use", "resource": "google" }
]
}
}
```
@@ -118,10 +126,12 @@ To replace `enabled_providers`, deny all providers first and allow the selected
```json title="opencode.json"
{
"policies": [
{ "effect": "deny", "action": "provider.use", "resource": "*" },
{ "effect": "allow", "action": "provider.use", "resource": "anthropic" },
{ "effect": "allow", "action": "provider.use", "resource": "openai" }
]
"experimental": {
"policies": [
{ "effect": "deny", "action": "provider.use", "resource": "*" },
{ "effect": "allow", "action": "provider.use", "resource": "anthropic" },
{ "effect": "allow", "action": "provider.use", "resource": "openai" }
]
}
}
```