@gotgenes/pi-permission-system 5.5.0 → 5.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,13 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
2
 
3
3
  import { evaluateBashExternalDirectoryGate } from "../../../src/handlers/gates/bash-external-directory";
4
- import type { ToolCallContext } from "../../../src/handlers/gates/types";
5
- import type { HandlerDeps } from "../../../src/handlers/types";
6
- import type { PermissionEventBus } from "../../../src/permission-events";
4
+ import type {
5
+ BashExternalDirectoryGateDeps,
6
+ ToolCallContext,
7
+ } from "../../../src/handlers/gates/types";
7
8
  import type { PermissionCheckResult } from "../../../src/types";
8
9
 
9
- // ── helpers ────────────────────────────────────────────────────────────────
10
+ // ── helpers ─────────────��───────────────────────────────────────────���──────
10
11
 
11
12
  function makeTcc(overrides: Partial<ToolCallContext> = {}): ToolCallContext {
12
13
  return {
@@ -32,91 +33,70 @@ function makeCheckResult(
32
33
  };
33
34
  }
34
35
 
35
- function makeEvents(): PermissionEventBus {
36
- return { emit: vi.fn(), on: vi.fn().mockReturnValue(() => undefined) };
37
- }
38
-
39
- function makeRuntime(
40
- overrides: Record<string, unknown> = {},
41
- ): HandlerDeps["runtime"] {
36
+ function makeBashExtGateDeps(
37
+ overrides: Partial<BashExternalDirectoryGateDeps> = {},
38
+ ): BashExternalDirectoryGateDeps {
42
39
  return {
43
- config: { debugLog: false, permissionReviewLog: true, yoloMode: false },
44
- runtimeContext: {} as HandlerDeps["runtime"]["runtimeContext"],
45
- permissionManager: {
46
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
47
- },
48
- sessionRules: {
49
- approve: vi.fn(),
50
- getRuleset: vi.fn().mockReturnValue([]),
51
- clear: vi.fn(),
52
- },
40
+ checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
41
+ getSessionRuleset: vi.fn().mockReturnValue([]),
42
+ approveSessionRule: vi.fn(),
53
43
  writeReviewLog: vi.fn(),
54
- ...overrides,
55
- } as unknown as HandlerDeps["runtime"];
56
- }
57
-
58
- function makeDeps(overrides: Record<string, unknown> = {}): HandlerDeps {
59
- const { runtime: runtimeOverrides, events, ...rest } = overrides;
60
- return {
61
- runtime: makeRuntime(runtimeOverrides as Record<string, unknown>),
62
- events: events ?? makeEvents(),
63
- canRequestPermissionConfirmation: vi.fn().mockReturnValue(true),
44
+ canConfirm: vi.fn().mockReturnValue(true),
64
45
  promptPermission: vi
65
46
  .fn()
66
47
  .mockResolvedValue({ approved: true, state: "approved" }),
67
- ...rest,
68
- } as unknown as HandlerDeps;
48
+ ...overrides,
49
+ };
69
50
  }
70
51
 
71
- // ── tests ──────────────────────────────────────────────────────────────────
52
+ // ── tests ─────────────────────────────��───────────────────────────────���────
72
53
 
73
54
  describe("evaluateBashExternalDirectoryGate", () => {
74
55
  it("returns null when tool is not bash", async () => {
75
56
  const tcc = makeTcc({ toolName: "read" });
76
- const result = await evaluateBashExternalDirectoryGate(tcc, makeDeps());
57
+ const result = await evaluateBashExternalDirectoryGate(
58
+ tcc,
59
+ makeBashExtGateDeps(),
60
+ );
77
61
  expect(result).toBeNull();
78
62
  });
79
63
 
80
64
  it("returns null when no CWD", async () => {
81
65
  const tcc = makeTcc({ cwd: undefined });
82
- const result = await evaluateBashExternalDirectoryGate(tcc, makeDeps());
66
+ const result = await evaluateBashExternalDirectoryGate(
67
+ tcc,
68
+ makeBashExtGateDeps(),
69
+ );
83
70
  expect(result).toBeNull();
84
71
  });
85
72
 
86
73
  it("returns null when command has no external paths", async () => {
87
74
  const tcc = makeTcc({ input: { command: "ls -la" } });
88
- const result = await evaluateBashExternalDirectoryGate(tcc, makeDeps());
75
+ const result = await evaluateBashExternalDirectoryGate(
76
+ tcc,
77
+ makeBashExtGateDeps(),
78
+ );
89
79
  expect(result).toBeNull();
90
80
  });
91
81
 
92
82
  it("returns null and logs when all external paths are session-covered", async () => {
93
- const writeReviewLog = vi.fn();
94
- const deps = makeDeps({
95
- runtime: {
96
- permissionManager: {
97
- checkPermission: vi
98
- .fn()
99
- .mockReturnValue(makeCheckResult("allow", { source: "session" })),
100
- },
101
- writeReviewLog,
102
- },
83
+ const deps = makeBashExtGateDeps({
84
+ checkPermission: vi
85
+ .fn()
86
+ .mockReturnValue(makeCheckResult("allow", { source: "session" })),
103
87
  });
104
88
  const tcc = makeTcc();
105
89
  const result = await evaluateBashExternalDirectoryGate(tcc, deps);
106
90
  expect(result).toBeNull();
107
- expect(writeReviewLog).toHaveBeenCalledWith(
91
+ expect(deps.writeReviewLog).toHaveBeenCalledWith(
108
92
  "permission_request.session_approved",
109
93
  expect.objectContaining({ resolution: "session_approved" }),
110
94
  );
111
95
  });
112
96
 
113
97
  it("blocks when policy is deny", async () => {
114
- const deps = makeDeps({
115
- runtime: {
116
- permissionManager: {
117
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("deny")),
118
- },
119
- },
98
+ const deps = makeBashExtGateDeps({
99
+ checkPermission: vi.fn().mockReturnValue(makeCheckResult("deny")),
120
100
  });
121
101
  const tcc = makeTcc();
122
102
  const result = await evaluateBashExternalDirectoryGate(tcc, deps);
@@ -124,18 +104,8 @@ describe("evaluateBashExternalDirectoryGate", () => {
124
104
  });
125
105
 
126
106
  it("allows without recording session rules when user approves once", async () => {
127
- const sessionRules = {
128
- approve: vi.fn(),
129
- getRuleset: vi.fn().mockReturnValue([]),
130
- clear: vi.fn(),
131
- };
132
- const deps = makeDeps({
133
- runtime: {
134
- permissionManager: {
135
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
136
- },
137
- sessionRules,
138
- },
107
+ const deps = makeBashExtGateDeps({
108
+ checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
139
109
  promptPermission: vi
140
110
  .fn()
141
111
  .mockResolvedValue({ approved: true, state: "approved" }),
@@ -143,22 +113,12 @@ describe("evaluateBashExternalDirectoryGate", () => {
143
113
  const tcc = makeTcc();
144
114
  const result = await evaluateBashExternalDirectoryGate(tcc, deps);
145
115
  expect(result).toEqual({ action: "allow" });
146
- expect(sessionRules.approve).not.toHaveBeenCalled();
116
+ expect(deps.approveSessionRule).not.toHaveBeenCalled();
147
117
  });
148
118
 
149
119
  it("records one session rule per uncovered path on approved_for_session", async () => {
150
- const sessionRules = {
151
- approve: vi.fn(),
152
- getRuleset: vi.fn().mockReturnValue([]),
153
- clear: vi.fn(),
154
- };
155
- const deps = makeDeps({
156
- runtime: {
157
- permissionManager: {
158
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
159
- },
160
- sessionRules,
161
- },
120
+ const deps = makeBashExtGateDeps({
121
+ checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
162
122
  promptPermission: vi
163
123
  .fn()
164
124
  .mockResolvedValue({ approved: true, state: "approved_for_session" }),
@@ -172,20 +132,16 @@ describe("evaluateBashExternalDirectoryGate", () => {
172
132
  const result = await evaluateBashExternalDirectoryGate(tcc, deps);
173
133
  expect(result).toEqual({ action: "allow" });
174
134
  // Each uncovered path gets its own session rule
175
- expect(sessionRules.approve).toHaveBeenCalledTimes(2);
176
- for (const call of (sessionRules.approve as ReturnType<typeof vi.fn>).mock
177
- .calls) {
135
+ expect(deps.approveSessionRule).toHaveBeenCalledTimes(2);
136
+ for (const call of (deps.approveSessionRule as ReturnType<typeof vi.fn>)
137
+ .mock.calls) {
178
138
  expect(call[0]).toBe("external_directory");
179
139
  }
180
140
  });
181
141
 
182
142
  it("blocks when user denies", async () => {
183
- const deps = makeDeps({
184
- runtime: {
185
- permissionManager: {
186
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
187
- },
188
- },
143
+ const deps = makeBashExtGateDeps({
144
+ checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
189
145
  promptPermission: vi
190
146
  .fn()
191
147
  .mockResolvedValue({ approved: false, state: "denied" }),
@@ -196,13 +152,9 @@ describe("evaluateBashExternalDirectoryGate", () => {
196
152
  });
197
153
 
198
154
  it("blocks when no UI available", async () => {
199
- const deps = makeDeps({
200
- runtime: {
201
- permissionManager: {
202
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
203
- },
204
- },
205
- canRequestPermissionConfirmation: vi.fn().mockReturnValue(false),
155
+ const deps = makeBashExtGateDeps({
156
+ checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
157
+ canConfirm: vi.fn().mockReturnValue(false),
206
158
  });
207
159
  const tcc = makeTcc();
208
160
  const result = await evaluateBashExternalDirectoryGate(tcc, deps);
@@ -210,8 +162,6 @@ describe("evaluateBashExternalDirectoryGate", () => {
210
162
  });
211
163
 
212
164
  it("only prompts about uncovered paths when some are session-covered", async () => {
213
- // First call (for getRuleset path filter): session covers /outside/a.ts
214
- // Second call (for config-level policy): returns ask
215
165
  const checkPermission = vi
216
166
  .fn()
217
167
  .mockImplementation(
@@ -228,14 +178,7 @@ describe("evaluateBashExternalDirectoryGate", () => {
228
178
  return makeCheckResult("ask");
229
179
  },
230
180
  );
231
- const deps = makeDeps({
232
- runtime: {
233
- permissionManager: { checkPermission },
234
- },
235
- promptPermission: vi
236
- .fn()
237
- .mockResolvedValue({ approved: true, state: "approved" }),
238
- });
181
+ const deps = makeBashExtGateDeps({ checkPermission });
239
182
  const tcc = makeTcc({
240
183
  input: { command: "diff /outside/a.ts /outside/b.ts" },
241
184
  });
@@ -1,9 +1,10 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
2
 
3
3
  import { evaluateExternalDirectoryGate } from "../../../src/handlers/gates/external-directory";
4
- import type { ToolCallContext } from "../../../src/handlers/gates/types";
5
- import type { HandlerDeps } from "../../../src/handlers/types";
6
- import type { PermissionEventBus } from "../../../src/permission-events";
4
+ import type {
5
+ ExternalDirectoryGateDeps,
6
+ ToolCallContext,
7
+ } from "../../../src/handlers/gates/types";
7
8
  import type { PermissionCheckResult } from "../../../src/types";
8
9
 
9
10
  // ── helpers ────────────────────────────────────────────────────────────────
@@ -32,41 +33,24 @@ function makeCheckResult(
32
33
  };
33
34
  }
34
35
 
35
- function makeEvents(): PermissionEventBus {
36
- return { emit: vi.fn(), on: vi.fn().mockReturnValue(() => undefined) };
37
- }
38
-
39
- function makeRuntime(
40
- overrides: Record<string, unknown> = {},
41
- ): HandlerDeps["runtime"] {
36
+ function makeExtDirGateDeps(
37
+ overrides: Partial<ExternalDirectoryGateDeps> = {},
38
+ ): ExternalDirectoryGateDeps {
42
39
  return {
43
- piInfrastructureDirs: ["/test/agent", "/test/agent/git"],
44
- config: { debugLog: false, permissionReviewLog: true, yoloMode: false },
45
- runtimeContext: {} as HandlerDeps["runtime"]["runtimeContext"],
46
- permissionManager: {
47
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
48
- },
49
- sessionRules: {
50
- approve: vi.fn(),
51
- getRuleset: vi.fn().mockReturnValue([]),
52
- clear: vi.fn(),
53
- },
40
+ checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
41
+ getSessionRuleset: vi.fn().mockReturnValue([]),
42
+ approveSessionRule: vi.fn(),
54
43
  writeReviewLog: vi.fn(),
55
- ...overrides,
56
- } as unknown as HandlerDeps["runtime"];
57
- }
58
-
59
- function makeDeps(overrides: Record<string, unknown> = {}): HandlerDeps {
60
- const { runtime: runtimeOverrides, events, ...rest } = overrides;
61
- return {
62
- runtime: makeRuntime(runtimeOverrides as Record<string, unknown>),
63
- events: events ?? makeEvents(),
64
- canRequestPermissionConfirmation: vi.fn().mockReturnValue(true),
44
+ emitDecision: vi.fn(),
45
+ canConfirm: vi.fn().mockReturnValue(true),
65
46
  promptPermission: vi
66
47
  .fn()
67
48
  .mockResolvedValue({ approved: true, state: "approved" }),
68
- ...rest,
69
- } as unknown as HandlerDeps;
49
+ getInfrastructureDirs: vi
50
+ .fn()
51
+ .mockReturnValue(["/test/agent", "/test/agent/git"]),
52
+ ...overrides,
53
+ };
70
54
  }
71
55
 
72
56
  // ── tests ──────────────────────────────────────────────────────────────────
@@ -74,35 +58,42 @@ function makeDeps(overrides: Record<string, unknown> = {}): HandlerDeps {
74
58
  describe("evaluateExternalDirectoryGate", () => {
75
59
  it("returns null when no CWD", async () => {
76
60
  const tcc = makeTcc({ cwd: undefined });
77
- const result = await evaluateExternalDirectoryGate(tcc, makeDeps());
61
+ const result = await evaluateExternalDirectoryGate(
62
+ tcc,
63
+ makeExtDirGateDeps(),
64
+ );
78
65
  expect(result).toBeNull();
79
66
  });
80
67
 
81
68
  it("returns null when tool is not path-bearing", async () => {
82
69
  const tcc = makeTcc({ toolName: "bash", input: { command: "ls" } });
83
- const result = await evaluateExternalDirectoryGate(tcc, makeDeps());
70
+ const result = await evaluateExternalDirectoryGate(
71
+ tcc,
72
+ makeExtDirGateDeps(),
73
+ );
84
74
  expect(result).toBeNull();
85
75
  });
86
76
 
87
77
  it("returns null when path is inside CWD", async () => {
88
78
  const tcc = makeTcc({ input: { path: "/test/project/src/index.ts" } });
89
- const result = await evaluateExternalDirectoryGate(tcc, makeDeps());
79
+ const result = await evaluateExternalDirectoryGate(
80
+ tcc,
81
+ makeExtDirGateDeps(),
82
+ );
90
83
  expect(result).toBeNull();
91
84
  });
92
85
 
93
86
  // ── Pi infrastructure read bypass ──────────────────────────────────────
94
87
 
95
88
  it("allows and emits infrastructure_auto_allowed for read targeting infra dir", async () => {
96
- const events = makeEvents();
97
- const deps = makeDeps({ events });
89
+ const deps = makeExtDirGateDeps();
98
90
  const tcc = makeTcc({
99
91
  toolName: "read",
100
92
  input: { path: "/test/agent/git/some-package/SKILL.md" },
101
93
  });
102
94
  const result = await evaluateExternalDirectoryGate(tcc, deps);
103
95
  expect(result).toEqual({ action: "allow" });
104
- expect(events.emit).toHaveBeenCalledWith(
105
- "permissions:decision",
96
+ expect(deps.emitDecision).toHaveBeenCalledWith(
106
97
  expect.objectContaining({
107
98
  resolution: "infrastructure_auto_allowed",
108
99
  result: "allow",
@@ -111,18 +102,8 @@ describe("evaluateExternalDirectoryGate", () => {
111
102
  });
112
103
 
113
104
  it("respects config.piInfrastructureReadPaths for bypass", async () => {
114
- const events = makeEvents();
115
- const deps = makeDeps({
116
- runtime: {
117
- piInfrastructureDirs: [],
118
- config: {
119
- debugLog: false,
120
- permissionReviewLog: true,
121
- yoloMode: false,
122
- piInfrastructureReadPaths: ["/custom/infra"],
123
- },
124
- },
125
- events,
105
+ const deps = makeExtDirGateDeps({
106
+ getInfrastructureDirs: vi.fn().mockReturnValue(["/custom/infra"]),
126
107
  });
127
108
  const tcc = makeTcc({
128
109
  toolName: "read",
@@ -133,12 +114,8 @@ describe("evaluateExternalDirectoryGate", () => {
133
114
  });
134
115
 
135
116
  it("does NOT bypass for write tools targeting infra dirs", async () => {
136
- const deps = makeDeps({
137
- runtime: {
138
- permissionManager: {
139
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("deny")),
140
- },
141
- },
117
+ const deps = makeExtDirGateDeps({
118
+ checkPermission: vi.fn().mockReturnValue(makeCheckResult("deny")),
142
119
  });
143
120
  const tcc = makeTcc({
144
121
  toolName: "write",
@@ -151,36 +128,25 @@ describe("evaluateExternalDirectoryGate", () => {
151
128
  // ── Session-rule hit ─────────────────────────────────────────────────────
152
129
 
153
130
  it("allows and emits session_approved when session rule covers the path", async () => {
154
- const events = makeEvents();
155
- const deps = makeDeps({
156
- runtime: {
157
- permissionManager: {
158
- checkPermission: vi.fn().mockReturnValue(
159
- makeCheckResult("allow", {
160
- source: "session",
161
- matchedPattern: "/outside/project/*",
162
- }),
163
- ),
164
- },
165
- sessionRules: {
166
- approve: vi.fn(),
167
- getRuleset: vi.fn().mockReturnValue([
168
- {
169
- surface: "external_directory",
170
- pattern: "/outside/project/*",
171
- action: "allow",
172
- },
173
- ]),
174
- clear: vi.fn(),
131
+ const deps = makeExtDirGateDeps({
132
+ checkPermission: vi.fn().mockReturnValue(
133
+ makeCheckResult("allow", {
134
+ source: "session",
135
+ matchedPattern: "/outside/project/*",
136
+ }),
137
+ ),
138
+ getSessionRuleset: vi.fn().mockReturnValue([
139
+ {
140
+ surface: "external_directory",
141
+ pattern: "/outside/project/*",
142
+ action: "allow",
175
143
  },
176
- },
177
- events,
144
+ ]),
178
145
  });
179
146
  const tcc = makeTcc();
180
147
  const result = await evaluateExternalDirectoryGate(tcc, deps);
181
148
  expect(result).toEqual({ action: "allow" });
182
- expect(events.emit).toHaveBeenCalledWith(
183
- "permissions:decision",
149
+ expect(deps.emitDecision).toHaveBeenCalledWith(
184
150
  expect.objectContaining({
185
151
  resolution: "session_approved",
186
152
  matchedPattern: "/outside/project/*",
@@ -191,20 +157,13 @@ describe("evaluateExternalDirectoryGate", () => {
191
157
  // ── Policy deny ──────────────────────────────────────────────────────────
192
158
 
193
159
  it("blocks and emits policy_deny when policy is deny", async () => {
194
- const events = makeEvents();
195
- const deps = makeDeps({
196
- runtime: {
197
- permissionManager: {
198
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("deny")),
199
- },
200
- },
201
- events,
160
+ const deps = makeExtDirGateDeps({
161
+ checkPermission: vi.fn().mockReturnValue(makeCheckResult("deny")),
202
162
  });
203
163
  const tcc = makeTcc();
204
164
  const result = await evaluateExternalDirectoryGate(tcc, deps);
205
165
  expect(result).toMatchObject({ action: "block" });
206
- expect(events.emit).toHaveBeenCalledWith(
207
- "permissions:decision",
166
+ expect(deps.emitDecision).toHaveBeenCalledWith(
208
167
  expect.objectContaining({
209
168
  surface: "external_directory",
210
169
  result: "deny",
@@ -216,18 +175,8 @@ describe("evaluateExternalDirectoryGate", () => {
216
175
  // ── Policy ask — user approves once ──────────────────────────────────────
217
176
 
218
177
  it("allows without recording session rule when user approves once", async () => {
219
- const sessionRules = {
220
- approve: vi.fn(),
221
- getRuleset: vi.fn().mockReturnValue([]),
222
- clear: vi.fn(),
223
- };
224
- const deps = makeDeps({
225
- runtime: {
226
- permissionManager: {
227
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
228
- },
229
- sessionRules,
230
- },
178
+ const deps = makeExtDirGateDeps({
179
+ checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
231
180
  promptPermission: vi
232
181
  .fn()
233
182
  .mockResolvedValue({ approved: true, state: "approved" }),
@@ -235,24 +184,14 @@ describe("evaluateExternalDirectoryGate", () => {
235
184
  const tcc = makeTcc();
236
185
  const result = await evaluateExternalDirectoryGate(tcc, deps);
237
186
  expect(result).toEqual({ action: "allow" });
238
- expect(sessionRules.approve).not.toHaveBeenCalled();
187
+ expect(deps.approveSessionRule).not.toHaveBeenCalled();
239
188
  });
240
189
 
241
190
  // ── Policy ask — user approves for session ───────────────────────────────
242
191
 
243
192
  it("records session rule when user approves for session", async () => {
244
- const sessionRules = {
245
- approve: vi.fn(),
246
- getRuleset: vi.fn().mockReturnValue([]),
247
- clear: vi.fn(),
248
- };
249
- const deps = makeDeps({
250
- runtime: {
251
- permissionManager: {
252
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
253
- },
254
- sessionRules,
255
- },
193
+ const deps = makeExtDirGateDeps({
194
+ checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
256
195
  promptPermission: vi
257
196
  .fn()
258
197
  .mockResolvedValue({ approved: true, state: "approved_for_session" }),
@@ -260,7 +199,7 @@ describe("evaluateExternalDirectoryGate", () => {
260
199
  const tcc = makeTcc();
261
200
  const result = await evaluateExternalDirectoryGate(tcc, deps);
262
201
  expect(result).toEqual({ action: "allow" });
263
- expect(sessionRules.approve).toHaveBeenCalledWith(
202
+ expect(deps.approveSessionRule).toHaveBeenCalledWith(
264
203
  "external_directory",
265
204
  expect.any(String),
266
205
  );
@@ -269,14 +208,8 @@ describe("evaluateExternalDirectoryGate", () => {
269
208
  // ── Policy ask — user denies ─────────────────────────────────────────────
270
209
 
271
210
  it("blocks and emits user_denied when user denies", async () => {
272
- const events = makeEvents();
273
- const deps = makeDeps({
274
- runtime: {
275
- permissionManager: {
276
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
277
- },
278
- },
279
- events,
211
+ const deps = makeExtDirGateDeps({
212
+ checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
280
213
  promptPermission: vi
281
214
  .fn()
282
215
  .mockResolvedValue({ approved: false, state: "denied" }),
@@ -284,8 +217,7 @@ describe("evaluateExternalDirectoryGate", () => {
284
217
  const tcc = makeTcc();
285
218
  const result = await evaluateExternalDirectoryGate(tcc, deps);
286
219
  expect(result).toMatchObject({ action: "block" });
287
- expect(events.emit).toHaveBeenCalledWith(
288
- "permissions:decision",
220
+ expect(deps.emitDecision).toHaveBeenCalledWith(
289
221
  expect.objectContaining({
290
222
  result: "deny",
291
223
  resolution: "user_denied",
@@ -296,21 +228,14 @@ describe("evaluateExternalDirectoryGate", () => {
296
228
  // ── Policy ask — no UI ───────────────────────────────────────────────────
297
229
 
298
230
  it("blocks and emits confirmation_unavailable when no UI", async () => {
299
- const events = makeEvents();
300
- const deps = makeDeps({
301
- runtime: {
302
- permissionManager: {
303
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
304
- },
305
- },
306
- events,
307
- canRequestPermissionConfirmation: vi.fn().mockReturnValue(false),
231
+ const deps = makeExtDirGateDeps({
232
+ checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
233
+ canConfirm: vi.fn().mockReturnValue(false),
308
234
  });
309
235
  const tcc = makeTcc();
310
236
  const result = await evaluateExternalDirectoryGate(tcc, deps);
311
237
  expect(result).toMatchObject({ action: "block" });
312
- expect(events.emit).toHaveBeenCalledWith(
313
- "permissions:decision",
238
+ expect(deps.emitDecision).toHaveBeenCalledWith(
314
239
  expect.objectContaining({
315
240
  result: "deny",
316
241
  resolution: "confirmation_unavailable",