Skip to content

Commit 1b41b09

Browse files
committed
fix(core): preserve denied permission context
1 parent 9a61dfb commit 1b41b09

10 files changed

Lines changed: 101 additions & 11 deletions

packages/core/src/permission.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,13 @@ export class CorrectedError extends Schema.TaggedErrorClass<CorrectedError>()("P
7474

7575
export class DeniedError extends Schema.TaggedErrorClass<DeniedError>()("PermissionV2.DeniedError", {
7676
rules: Permission.Ruleset,
77-
}) {}
77+
permission: Schema.String,
78+
resources: Schema.Array(Schema.String),
79+
}) {
80+
override get message() {
81+
return `Permission denied: ${this.permission}`
82+
}
83+
}
7884

7985
export class NotFoundError extends Schema.TaggedErrorClass<NotFoundError>()("PermissionV2.NotFoundError", {
8086
requestID: ID,
@@ -216,6 +222,8 @@ const layer = Layer.effect(
216222
if (result.effect === "deny") {
217223
return yield* new DeniedError({
218224
rules: relevant(input, result.rules),
225+
permission: input.action,
226+
resources: input.resources,
219227
})
220228
}
221229
if (result.effect === "allow") return

packages/core/src/session/to-session-error.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export function toSessionError(cause: unknown): SessionError.Error {
4040
}
4141
}
4242
}
43-
if (cause instanceof PermissionV2.RejectedError)
43+
if (cause instanceof PermissionV2.DeniedError || cause instanceof PermissionV2.RejectedError)
4444
return {
4545
type: "permission.rejected",
4646
message: cause.message,

packages/core/test/tool-apply-patch.test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,15 @@ const permission = Layer.succeed(
4747
}).pipe(
4848
Effect.andThen(input.action === "edit" ? Effect.suspend(afterEditApproval) : Effect.void),
4949
Effect.andThen(
50-
input.action === denyAction ? Effect.fail(new PermissionV2.DeniedError({ rules: [] })) : Effect.void,
50+
input.action === denyAction
51+
? Effect.fail(
52+
new PermissionV2.DeniedError({
53+
rules: [],
54+
permission: input.action,
55+
resources: input.resources,
56+
}),
57+
)
58+
: Effect.void,
5159
),
5260
),
5361
ask: () => Effect.die("unused"),

packages/core/test/tool-edit.test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,15 @@ const permission = Layer.succeed(
4040
assert: (input) =>
4141
Effect.sync(() => assertions.push(input)).pipe(
4242
Effect.andThen(
43-
input.action === denyAction ? Effect.fail(new PermissionV2.DeniedError({ rules: [] })) : Effect.void,
43+
input.action === denyAction
44+
? Effect.fail(
45+
new PermissionV2.DeniedError({
46+
rules: [],
47+
permission: input.action,
48+
resources: input.resources,
49+
}),
50+
)
51+
: Effect.void,
4452
),
4553
),
4654
ask: () => Effect.die("unused"),

packages/core/test/tool-question.test.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,17 @@ const permission = Layer.succeed(
2323
PermissionV2.Service.of({
2424
assert: (input) =>
2525
Effect.sync(() => assertions.push(input)).pipe(
26-
Effect.andThen(deny ? Effect.fail(new PermissionV2.DeniedError({ rules: [] })) : Effect.void),
26+
Effect.andThen(
27+
deny
28+
? Effect.fail(
29+
new PermissionV2.DeniedError({
30+
rules: [],
31+
permission: input.action,
32+
resources: input.resources,
33+
}),
34+
)
35+
: Effect.void,
36+
),
2737
),
2838
ask: () => Effect.die("unused"),
2939
reply: () => Effect.die("unused"),
@@ -72,7 +82,15 @@ describe("QuestionTool", () => {
7282
...toolIdentity,
7383
call: { type: "tool-call", id: "call-question-denied", name: "question", input: { questions: [] } },
7484
}),
75-
).toEqual({ result: { type: "error", value: "Permission denied: question" } })
85+
).toEqual({
86+
result: { type: "error", value: "Permission denied: question" },
87+
error: {
88+
type: "permission.rejected",
89+
message: "Permission denied: question",
90+
permission: "question",
91+
resources: ["*"],
92+
},
93+
})
7694
expect(capturedInput()).toBeUndefined()
7795
deny = false
7896
}),

packages/core/test/tool-read.test.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,19 @@ const permission = Layer.succeed(
8181
assert: (input) =>
8282
Effect.sync(() => {
8383
assertions.push(input)
84-
}).pipe(Effect.andThen(allow ? Effect.void : Effect.fail(new PermissionV2.DeniedError({ rules: [] })))),
84+
}).pipe(
85+
Effect.andThen(
86+
allow
87+
? Effect.void
88+
: Effect.fail(
89+
new PermissionV2.DeniedError({
90+
rules: [],
91+
permission: input.action,
92+
resources: input.resources,
93+
}),
94+
),
95+
),
96+
),
8597
ask: () => Effect.die("unused"),
8698
reply: () => Effect.die("unused"),
8799
get: () => Effect.die("unused"),

packages/core/test/tool-shell.test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,15 @@ const permission = Layer.succeed(
4747
Effect.sync(() => assertions.push(input)).pipe(
4848
Effect.andThen(Effect.suspend(() => afterPermission(input))),
4949
Effect.andThen(
50-
input.action === denyAction ? Effect.fail(new PermissionV2.DeniedError({ rules: [] })) : Effect.void,
50+
input.action === denyAction
51+
? Effect.fail(
52+
new PermissionV2.DeniedError({
53+
rules: [],
54+
permission: input.action,
55+
resources: input.resources,
56+
}),
57+
)
58+
: Effect.void,
5159
),
5260
),
5361
ask: () => Effect.die("unused"),

packages/core/test/tool-skill.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,17 @@ describe("SkillTool", () => {
5555
PermissionV2.Service.of({
5656
assert: (input) =>
5757
Effect.sync(() => assertions.push(input)).pipe(
58-
Effect.andThen(deny ? Effect.fail(new PermissionV2.DeniedError({ rules: [] })) : Effect.void),
58+
Effect.andThen(
59+
deny
60+
? Effect.fail(
61+
new PermissionV2.DeniedError({
62+
rules: [],
63+
permission: input.action,
64+
resources: input.resources,
65+
}),
66+
)
67+
: Effect.void,
68+
),
5969
),
6070
ask: () => Effect.die("unused"),
6171
reply: () => Effect.die("unused"),

packages/core/test/tool-todowrite.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,17 @@ const permission = Layer.succeed(
3333
PermissionV2.Service.of({
3434
assert: (input) =>
3535
Effect.sync(() => assertions.push(input)).pipe(
36-
Effect.andThen(deny ? Effect.fail(new PermissionV2.DeniedError({ rules: [] })) : Effect.void),
36+
Effect.andThen(
37+
deny
38+
? Effect.fail(
39+
new PermissionV2.DeniedError({
40+
rules: [],
41+
permission: input.action,
42+
resources: input.resources,
43+
}),
44+
)
45+
: Effect.void,
46+
),
3747
),
3848
ask: () => Effect.die("unused"),
3949
reply: () => Effect.die("unused"),

packages/core/test/tool-write.test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,15 @@ const permission = Layer.succeed(
3838
assert: (input) =>
3939
Effect.sync(() => assertions.push(input)).pipe(
4040
Effect.andThen(
41-
input.action === denyAction ? Effect.fail(new PermissionV2.DeniedError({ rules: [] })) : Effect.void,
41+
input.action === denyAction
42+
? Effect.fail(
43+
new PermissionV2.DeniedError({
44+
rules: [],
45+
permission: input.action,
46+
resources: input.resources,
47+
}),
48+
)
49+
: Effect.void,
4250
),
4351
),
4452
ask: () => Effect.die("unused"),

0 commit comments

Comments
 (0)