@gotgenes/pi-permission-system 10.0.0 → 10.1.0
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.
- package/CHANGELOG.md +26 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/agent-prep-session.ts +28 -0
- package/src/decision-reporter.ts +41 -0
- package/src/denial-messages.ts +11 -0
- package/src/forwarded-permissions/permission-forwarder.ts +549 -0
- package/src/forwarding-manager.ts +3 -7
- package/src/gate-handler-session.ts +13 -0
- package/src/gate-prompter.ts +14 -0
- package/src/handlers/before-agent-start.ts +2 -3
- package/src/handlers/gates/bash-command.ts +4 -18
- package/src/handlers/gates/bash-external-directory.ts +3 -15
- package/src/handlers/gates/bash-path.ts +3 -16
- package/src/handlers/gates/descriptor.ts +0 -28
- package/src/handlers/gates/path.ts +3 -15
- package/src/handlers/gates/runner.ts +142 -105
- package/src/handlers/gates/skill-input-gate-pipeline.ts +104 -0
- package/src/handlers/gates/skill-input.ts +44 -0
- package/src/handlers/gates/tool-call-gate-pipeline.ts +120 -0
- package/src/handlers/lifecycle.ts +9 -9
- package/src/handlers/permission-gate-handler.ts +34 -238
- package/src/index.ts +49 -69
- package/src/mcp-targets.ts +56 -46
- package/src/permission-prompter.ts +7 -58
- package/src/permission-resolver.ts +17 -0
- package/src/permission-session.ts +77 -9
- package/src/permissions-service.ts +53 -0
- package/src/service-lifecycle.ts +49 -0
- package/src/session-approval-recorder.ts +6 -0
- package/src/session-lifecycle-session.ts +24 -0
- package/src/tool-input-preview.ts +0 -62
- package/src/tool-input-prompt-formatters.ts +63 -0
- package/src/tool-preview-formatter.ts +6 -4
- package/test/decision-reporter.test.ts +112 -0
- package/test/denial-messages.test.ts +62 -0
- package/test/forwarding-manager.test.ts +26 -44
- package/test/handlers/before-agent-start.test.ts +45 -21
- package/test/handlers/external-directory-integration.test.ts +86 -22
- package/test/handlers/external-directory-session-dedup.test.ts +102 -55
- package/test/handlers/gates/bash-command.test.ts +49 -90
- package/test/handlers/gates/bash-external-directory.test.ts +54 -95
- package/test/handlers/gates/bash-path.test.ts +63 -148
- package/test/handlers/gates/path.test.ts +38 -105
- package/test/handlers/gates/runner.test.ts +150 -93
- package/test/handlers/gates/skill-input-gate-pipeline.test.ts +176 -0
- package/test/handlers/gates/skill-input.test.ts +128 -0
- package/test/handlers/gates/tool-call-gate-pipeline.test.ts +180 -0
- package/test/handlers/input.test.ts +1 -2
- package/test/handlers/lifecycle.test.ts +49 -33
- package/test/handlers/tool-call-events.test.ts +1 -1
- package/test/helpers/gate-fixtures.ts +147 -16
- package/test/helpers/handler-fixtures.ts +143 -27
- package/test/mcp-targets.test.ts +55 -0
- package/test/permission-forwarder.test.ts +295 -0
- package/test/permission-forwarding.test.ts +0 -282
- package/test/permission-prompter.test.ts +33 -44
- package/test/permission-session.test.ts +160 -27
- package/test/permissions-service.test.ts +151 -0
- package/test/runtime.test.ts +0 -4
- package/test/service-lifecycle.test.ts +162 -0
- package/test/tool-input-preview.test.ts +0 -111
- package/test/tool-input-prompt-formatters.test.ts +115 -0
- package/src/forwarded-permissions/polling.ts +0 -411
|
@@ -2,20 +2,22 @@ import { describe, expect, it, vi } from "vitest";
|
|
|
2
2
|
|
|
3
3
|
import type { DenialContext } from "#src/denial-messages";
|
|
4
4
|
import { EXTENSION_TAG } from "#src/denial-messages";
|
|
5
|
-
import type {
|
|
6
|
-
|
|
5
|
+
import type {
|
|
6
|
+
GateBypass,
|
|
7
|
+
GateDescriptor,
|
|
8
|
+
} from "#src/handlers/gates/descriptor";
|
|
7
9
|
import { SessionApproval } from "#src/session-approval";
|
|
8
|
-
import { makeDescriptor,
|
|
10
|
+
import { makeDescriptor, makeGateRunner } from "#test/helpers/gate-fixtures";
|
|
9
11
|
import { makeCheckResult } from "#test/helpers/handler-fixtures";
|
|
10
12
|
|
|
11
|
-
// ──
|
|
13
|
+
// ── GateRunner — descriptor path ───────────────────────────────────────────
|
|
12
14
|
|
|
13
|
-
describe("
|
|
15
|
+
describe("GateRunner — descriptor path", () => {
|
|
14
16
|
it("returns allow and emits policy_allow when policy is allow", async () => {
|
|
15
|
-
const deps =
|
|
16
|
-
const result = await
|
|
17
|
+
const { runner, deps } = makeGateRunner();
|
|
18
|
+
const result = await runner.run(makeDescriptor(), null, "tc-1");
|
|
17
19
|
expect(result).toEqual({ action: "allow" });
|
|
18
|
-
expect(deps.emitDecision).toHaveBeenCalledWith(
|
|
20
|
+
expect(deps.reporter.emitDecision).toHaveBeenCalledWith(
|
|
19
21
|
expect.objectContaining({
|
|
20
22
|
surface: "read",
|
|
21
23
|
value: "read",
|
|
@@ -26,36 +28,36 @@ describe("runGateCheck", () => {
|
|
|
26
28
|
});
|
|
27
29
|
|
|
28
30
|
it("returns block and emits policy_deny when policy is deny", async () => {
|
|
29
|
-
const deps =
|
|
30
|
-
|
|
31
|
+
const { runner, deps } = makeGateRunner({
|
|
32
|
+
resolve: vi
|
|
31
33
|
.fn()
|
|
32
34
|
.mockReturnValue(
|
|
33
35
|
makeCheckResult({ state: "deny", matchedPattern: "*" }),
|
|
34
36
|
),
|
|
35
37
|
});
|
|
36
|
-
const result = await
|
|
38
|
+
const result = await runner.run(makeDescriptor(), null, "tc-1");
|
|
37
39
|
expect(result).toMatchObject({ action: "block" });
|
|
38
|
-
expect(deps.emitDecision).toHaveBeenCalledWith(
|
|
40
|
+
expect(deps.reporter.emitDecision).toHaveBeenCalledWith(
|
|
39
41
|
expect.objectContaining({
|
|
40
42
|
result: "deny",
|
|
41
43
|
resolution: "policy_deny",
|
|
42
44
|
}),
|
|
43
45
|
);
|
|
44
|
-
expect(deps.writeReviewLog).toHaveBeenCalledWith(
|
|
46
|
+
expect(deps.reporter.writeReviewLog).toHaveBeenCalledWith(
|
|
45
47
|
"permission_request.blocked",
|
|
46
48
|
expect.objectContaining({ resolution: "policy_denied" }),
|
|
47
49
|
);
|
|
48
50
|
});
|
|
49
51
|
|
|
50
52
|
it("returns allow and emits session_approved on session hit", async () => {
|
|
51
|
-
const deps =
|
|
52
|
-
|
|
53
|
+
const { runner, deps } = makeGateRunner({
|
|
54
|
+
resolve: vi
|
|
53
55
|
.fn()
|
|
54
56
|
.mockReturnValue(
|
|
55
57
|
makeCheckResult({ source: "session", matchedPattern: "git *" }),
|
|
56
58
|
),
|
|
57
59
|
});
|
|
58
|
-
const result = await
|
|
60
|
+
const result = await runner.run(
|
|
59
61
|
makeDescriptor({
|
|
60
62
|
surface: "bash",
|
|
61
63
|
input: { command: "git status" },
|
|
@@ -63,17 +65,16 @@ describe("runGateCheck", () => {
|
|
|
63
65
|
}),
|
|
64
66
|
null,
|
|
65
67
|
"tc-1",
|
|
66
|
-
deps,
|
|
67
68
|
);
|
|
68
69
|
expect(result).toEqual({ action: "allow" });
|
|
69
|
-
expect(deps.writeReviewLog).toHaveBeenCalledWith(
|
|
70
|
+
expect(deps.reporter.writeReviewLog).toHaveBeenCalledWith(
|
|
70
71
|
"permission_request.session_approved",
|
|
71
72
|
expect.objectContaining({
|
|
72
73
|
resolution: "session_approved",
|
|
73
74
|
sessionApprovalPattern: "git *",
|
|
74
75
|
}),
|
|
75
76
|
);
|
|
76
|
-
expect(deps.emitDecision).toHaveBeenCalledWith(
|
|
77
|
+
expect(deps.reporter.emitDecision).toHaveBeenCalledWith(
|
|
77
78
|
expect.objectContaining({
|
|
78
79
|
resolution: "session_approved",
|
|
79
80
|
matchedPattern: "git *",
|
|
@@ -82,8 +83,8 @@ describe("runGateCheck", () => {
|
|
|
82
83
|
});
|
|
83
84
|
|
|
84
85
|
it("returns allow and emits user_approved when ask + user approves", async () => {
|
|
85
|
-
const deps =
|
|
86
|
-
|
|
86
|
+
const { runner, deps } = makeGateRunner({
|
|
87
|
+
resolve: vi
|
|
87
88
|
.fn()
|
|
88
89
|
.mockReturnValue(
|
|
89
90
|
makeCheckResult({ state: "ask", matchedPattern: "*" }),
|
|
@@ -92,9 +93,9 @@ describe("runGateCheck", () => {
|
|
|
92
93
|
.fn()
|
|
93
94
|
.mockResolvedValue({ approved: true, state: "approved" }),
|
|
94
95
|
});
|
|
95
|
-
const result = await
|
|
96
|
+
const result = await runner.run(makeDescriptor(), null, "tc-1");
|
|
96
97
|
expect(result).toEqual({ action: "allow" });
|
|
97
|
-
expect(deps.emitDecision).toHaveBeenCalledWith(
|
|
98
|
+
expect(deps.reporter.emitDecision).toHaveBeenCalledWith(
|
|
98
99
|
expect.objectContaining({
|
|
99
100
|
result: "allow",
|
|
100
101
|
resolution: "user_approved",
|
|
@@ -103,8 +104,8 @@ describe("runGateCheck", () => {
|
|
|
103
104
|
});
|
|
104
105
|
|
|
105
106
|
it("returns allow, emits user_approved_for_session, and records session rule on approved_for_session", async () => {
|
|
106
|
-
const deps =
|
|
107
|
-
|
|
107
|
+
const { runner, deps } = makeGateRunner({
|
|
108
|
+
resolve: vi
|
|
108
109
|
.fn()
|
|
109
110
|
.mockReturnValue(
|
|
110
111
|
makeCheckResult({ state: "ask", matchedPattern: "*" }),
|
|
@@ -116,9 +117,9 @@ describe("runGateCheck", () => {
|
|
|
116
117
|
const descriptor = makeDescriptor({
|
|
117
118
|
sessionApproval: SessionApproval.single("read", "*"),
|
|
118
119
|
});
|
|
119
|
-
const result = await
|
|
120
|
+
const result = await runner.run(descriptor, null, "tc-1");
|
|
120
121
|
expect(result).toEqual({ action: "allow" });
|
|
121
|
-
expect(deps.emitDecision).toHaveBeenCalledWith(
|
|
122
|
+
expect(deps.reporter.emitDecision).toHaveBeenCalledWith(
|
|
122
123
|
expect.objectContaining({
|
|
123
124
|
resolution: "user_approved_for_session",
|
|
124
125
|
}),
|
|
@@ -129,8 +130,8 @@ describe("runGateCheck", () => {
|
|
|
129
130
|
});
|
|
130
131
|
|
|
131
132
|
it("calls recordSessionApproval once with the full SessionApproval when sessionApproval has multiple patterns", async () => {
|
|
132
|
-
const deps =
|
|
133
|
-
|
|
133
|
+
const { runner, deps } = makeGateRunner({
|
|
134
|
+
resolve: vi
|
|
134
135
|
.fn()
|
|
135
136
|
.mockReturnValue(
|
|
136
137
|
makeCheckResult({ state: "ask", matchedPattern: "*" }),
|
|
@@ -144,15 +145,15 @@ describe("runGateCheck", () => {
|
|
|
144
145
|
"/outside/b/*",
|
|
145
146
|
]);
|
|
146
147
|
const descriptor = makeDescriptor({ sessionApproval: approval });
|
|
147
|
-
const result = await
|
|
148
|
+
const result = await runner.run(descriptor, null, "tc-1");
|
|
148
149
|
expect(result).toEqual({ action: "allow" });
|
|
149
150
|
expect(deps.recordSessionApproval).toHaveBeenCalledTimes(1);
|
|
150
151
|
expect(deps.recordSessionApproval).toHaveBeenCalledWith(approval);
|
|
151
152
|
});
|
|
152
153
|
|
|
153
154
|
it("returns block and emits user_denied when ask + user denies", async () => {
|
|
154
|
-
const deps =
|
|
155
|
-
|
|
155
|
+
const { runner, deps } = makeGateRunner({
|
|
156
|
+
resolve: vi
|
|
156
157
|
.fn()
|
|
157
158
|
.mockReturnValue(
|
|
158
159
|
makeCheckResult({ state: "ask", matchedPattern: "*" }),
|
|
@@ -161,9 +162,9 @@ describe("runGateCheck", () => {
|
|
|
161
162
|
.fn()
|
|
162
163
|
.mockResolvedValue({ approved: false, state: "denied" }),
|
|
163
164
|
});
|
|
164
|
-
const result = await
|
|
165
|
+
const result = await runner.run(makeDescriptor(), null, "tc-1");
|
|
165
166
|
expect(result).toMatchObject({ action: "block" });
|
|
166
|
-
expect(deps.emitDecision).toHaveBeenCalledWith(
|
|
167
|
+
expect(deps.reporter.emitDecision).toHaveBeenCalledWith(
|
|
167
168
|
expect.objectContaining({
|
|
168
169
|
result: "deny",
|
|
169
170
|
resolution: "user_denied",
|
|
@@ -172,17 +173,17 @@ describe("runGateCheck", () => {
|
|
|
172
173
|
});
|
|
173
174
|
|
|
174
175
|
it("returns block and emits confirmation_unavailable when ask + no UI", async () => {
|
|
175
|
-
const deps =
|
|
176
|
-
|
|
176
|
+
const { runner, deps } = makeGateRunner({
|
|
177
|
+
resolve: vi
|
|
177
178
|
.fn()
|
|
178
179
|
.mockReturnValue(
|
|
179
180
|
makeCheckResult({ state: "ask", matchedPattern: "*" }),
|
|
180
181
|
),
|
|
181
182
|
canConfirm: vi.fn().mockReturnValue(false),
|
|
182
183
|
});
|
|
183
|
-
const result = await
|
|
184
|
+
const result = await runner.run(makeDescriptor(), null, "tc-1");
|
|
184
185
|
expect(result).toMatchObject({ action: "block" });
|
|
185
|
-
expect(deps.emitDecision).toHaveBeenCalledWith(
|
|
186
|
+
expect(deps.reporter.emitDecision).toHaveBeenCalledWith(
|
|
186
187
|
expect.objectContaining({
|
|
187
188
|
result: "deny",
|
|
188
189
|
resolution: "confirmation_unavailable",
|
|
@@ -191,8 +192,8 @@ describe("runGateCheck", () => {
|
|
|
191
192
|
});
|
|
192
193
|
|
|
193
194
|
it("emits auto_approved resolution when decision has autoApproved flag", async () => {
|
|
194
|
-
const deps =
|
|
195
|
-
|
|
195
|
+
const { runner, deps } = makeGateRunner({
|
|
196
|
+
resolve: vi
|
|
196
197
|
.fn()
|
|
197
198
|
.mockReturnValue(
|
|
198
199
|
makeCheckResult({ state: "ask", matchedPattern: "*" }),
|
|
@@ -203,61 +204,51 @@ describe("runGateCheck", () => {
|
|
|
203
204
|
autoApproved: true,
|
|
204
205
|
}),
|
|
205
206
|
});
|
|
206
|
-
const result = await
|
|
207
|
+
const result = await runner.run(makeDescriptor(), null, "tc-1");
|
|
207
208
|
expect(result).toEqual({ action: "allow" });
|
|
208
|
-
expect(deps.emitDecision).toHaveBeenCalledWith(
|
|
209
|
+
expect(deps.reporter.emitDecision).toHaveBeenCalledWith(
|
|
209
210
|
expect.objectContaining({
|
|
210
211
|
resolution: "auto_approved",
|
|
211
212
|
}),
|
|
212
213
|
);
|
|
213
214
|
});
|
|
214
215
|
|
|
215
|
-
it("uses preResolved.state instead of calling
|
|
216
|
-
const deps =
|
|
216
|
+
it("uses preResolved.state instead of calling resolve", async () => {
|
|
217
|
+
const { runner, deps } = makeGateRunner();
|
|
217
218
|
const descriptor = makeDescriptor({
|
|
218
219
|
preResolved: { state: "deny" },
|
|
219
220
|
});
|
|
220
|
-
const result = await
|
|
221
|
+
const result = await runner.run(descriptor, null, "tc-1");
|
|
221
222
|
expect(result).toMatchObject({ action: "block" });
|
|
222
|
-
expect(deps.
|
|
223
|
-
expect(deps.emitDecision).toHaveBeenCalledWith(
|
|
223
|
+
expect(deps.resolve).not.toHaveBeenCalled();
|
|
224
|
+
expect(deps.reporter.emitDecision).toHaveBeenCalledWith(
|
|
224
225
|
expect.objectContaining({
|
|
225
226
|
resolution: "policy_deny",
|
|
226
227
|
}),
|
|
227
228
|
);
|
|
228
229
|
});
|
|
229
230
|
|
|
230
|
-
it("uses preResolved.state allow without calling
|
|
231
|
-
const deps =
|
|
231
|
+
it("uses preResolved.state allow without calling resolve", async () => {
|
|
232
|
+
const { runner, deps } = makeGateRunner();
|
|
232
233
|
const descriptor = makeDescriptor({
|
|
233
234
|
preResolved: { state: "allow" },
|
|
234
235
|
});
|
|
235
|
-
const result = await
|
|
236
|
+
const result = await runner.run(descriptor, null, "tc-1");
|
|
236
237
|
expect(result).toEqual({ action: "allow" });
|
|
237
|
-
expect(deps.
|
|
238
|
-
expect(deps.emitDecision).toHaveBeenCalledWith(
|
|
238
|
+
expect(deps.resolve).not.toHaveBeenCalled();
|
|
239
|
+
expect(deps.reporter.emitDecision).toHaveBeenCalledWith(
|
|
239
240
|
expect.objectContaining({
|
|
240
241
|
resolution: "policy_allow",
|
|
241
242
|
}),
|
|
242
243
|
);
|
|
243
244
|
});
|
|
244
245
|
|
|
245
|
-
it("passes agentName to
|
|
246
|
-
const deps =
|
|
247
|
-
const result = await
|
|
248
|
-
makeDescriptor(),
|
|
249
|
-
"test-agent",
|
|
250
|
-
"tc-1",
|
|
251
|
-
deps,
|
|
252
|
-
);
|
|
246
|
+
it("passes agentName to resolve and decision event", async () => {
|
|
247
|
+
const { runner, deps } = makeGateRunner();
|
|
248
|
+
const result = await runner.run(makeDescriptor(), "test-agent", "tc-1");
|
|
253
249
|
expect(result).toEqual({ action: "allow" });
|
|
254
|
-
expect(deps.
|
|
255
|
-
|
|
256
|
-
{},
|
|
257
|
-
"test-agent",
|
|
258
|
-
[],
|
|
259
|
-
);
|
|
260
|
-
expect(deps.emitDecision).toHaveBeenCalledWith(
|
|
250
|
+
expect(deps.resolve).toHaveBeenCalledWith("read", {}, "test-agent");
|
|
251
|
+
expect(deps.reporter.emitDecision).toHaveBeenCalledWith(
|
|
261
252
|
expect.objectContaining({
|
|
262
253
|
agentName: "test-agent",
|
|
263
254
|
}),
|
|
@@ -265,22 +256,22 @@ describe("runGateCheck", () => {
|
|
|
265
256
|
});
|
|
266
257
|
|
|
267
258
|
it("passes requestId from toolCallId to promptPermission", async () => {
|
|
268
|
-
const deps =
|
|
269
|
-
|
|
259
|
+
const { runner, deps } = makeGateRunner({
|
|
260
|
+
resolve: vi
|
|
270
261
|
.fn()
|
|
271
262
|
.mockReturnValue(
|
|
272
263
|
makeCheckResult({ state: "ask", matchedPattern: "*" }),
|
|
273
264
|
),
|
|
274
265
|
});
|
|
275
|
-
await
|
|
266
|
+
await runner.run(makeDescriptor(), null, "tc-42");
|
|
276
267
|
expect(deps.promptPermission).toHaveBeenCalledWith(
|
|
277
268
|
expect.objectContaining({ requestId: "tc-42" }),
|
|
278
269
|
);
|
|
279
270
|
});
|
|
280
271
|
|
|
281
272
|
it("does not call recordSessionApproval when user approves once (no sessionApproval)", async () => {
|
|
282
|
-
const deps =
|
|
283
|
-
|
|
273
|
+
const { runner, deps } = makeGateRunner({
|
|
274
|
+
resolve: vi
|
|
284
275
|
.fn()
|
|
285
276
|
.mockReturnValue(
|
|
286
277
|
makeCheckResult({ state: "ask", matchedPattern: "*" }),
|
|
@@ -289,12 +280,12 @@ describe("runGateCheck", () => {
|
|
|
289
280
|
.fn()
|
|
290
281
|
.mockResolvedValue({ approved: true, state: "approved" }),
|
|
291
282
|
});
|
|
292
|
-
await
|
|
283
|
+
await runner.run(makeDescriptor(), null, "tc-1");
|
|
293
284
|
expect(deps.recordSessionApproval).not.toHaveBeenCalled();
|
|
294
285
|
});
|
|
295
286
|
|
|
296
|
-
it("uses preCheck result directly instead of calling
|
|
297
|
-
const deps =
|
|
287
|
+
it("uses preCheck result directly instead of calling resolve", async () => {
|
|
288
|
+
const { runner, deps } = makeGateRunner();
|
|
298
289
|
const descriptor = makeDescriptor({
|
|
299
290
|
preCheck: makeCheckResult({
|
|
300
291
|
state: "deny",
|
|
@@ -302,10 +293,10 @@ describe("runGateCheck", () => {
|
|
|
302
293
|
matchedPattern: "rm *",
|
|
303
294
|
}),
|
|
304
295
|
});
|
|
305
|
-
const result = await
|
|
296
|
+
const result = await runner.run(descriptor, null, "tc-1");
|
|
306
297
|
expect(result).toMatchObject({ action: "block" });
|
|
307
|
-
expect(deps.
|
|
308
|
-
expect(deps.emitDecision).toHaveBeenCalledWith(
|
|
298
|
+
expect(deps.resolve).not.toHaveBeenCalled();
|
|
299
|
+
expect(deps.reporter.emitDecision).toHaveBeenCalledWith(
|
|
309
300
|
expect.objectContaining({
|
|
310
301
|
resolution: "policy_deny",
|
|
311
302
|
origin: "global",
|
|
@@ -315,8 +306,8 @@ describe("runGateCheck", () => {
|
|
|
315
306
|
});
|
|
316
307
|
|
|
317
308
|
it("does not call recordSessionApproval when user approves for session but no sessionApproval on descriptor", async () => {
|
|
318
|
-
const deps =
|
|
319
|
-
|
|
309
|
+
const { runner, deps } = makeGateRunner({
|
|
310
|
+
resolve: vi
|
|
320
311
|
.fn()
|
|
321
312
|
.mockReturnValue(
|
|
322
313
|
makeCheckResult({ state: "ask", matchedPattern: "*" }),
|
|
@@ -326,7 +317,7 @@ describe("runGateCheck", () => {
|
|
|
326
317
|
.mockResolvedValue({ approved: true, state: "approved_for_session" }),
|
|
327
318
|
});
|
|
328
319
|
// No sessionApproval on descriptor
|
|
329
|
-
await
|
|
320
|
+
await runner.run(makeDescriptor(), null, "tc-1");
|
|
330
321
|
expect(deps.recordSessionApproval).not.toHaveBeenCalled();
|
|
331
322
|
});
|
|
332
323
|
|
|
@@ -360,8 +351,8 @@ describe("runGateCheck", () => {
|
|
|
360
351
|
}
|
|
361
352
|
|
|
362
353
|
it("uses denialContext to format denyReason with extension tag", async () => {
|
|
363
|
-
const
|
|
364
|
-
|
|
354
|
+
const { runner } = makeGateRunner({
|
|
355
|
+
resolve: vi
|
|
365
356
|
.fn()
|
|
366
357
|
.mockReturnValue(
|
|
367
358
|
makeCheckResult({ state: "deny", matchedPattern: "*" }),
|
|
@@ -372,11 +363,10 @@ describe("runGateCheck", () => {
|
|
|
372
363
|
check: makeCheckResult({ state: "deny", matchedPattern: "*" }),
|
|
373
364
|
agentName: "test-agent",
|
|
374
365
|
};
|
|
375
|
-
const result = await
|
|
366
|
+
const result = await runner.run(
|
|
376
367
|
makeDenialContextDescriptor(ctx),
|
|
377
368
|
"test-agent",
|
|
378
369
|
"tc-1",
|
|
379
|
-
deps,
|
|
380
370
|
);
|
|
381
371
|
expect(result.action).toBe("block");
|
|
382
372
|
if (result.action === "block") {
|
|
@@ -386,8 +376,8 @@ describe("runGateCheck", () => {
|
|
|
386
376
|
});
|
|
387
377
|
|
|
388
378
|
it("uses denialContext to format unavailableReason with extension tag", async () => {
|
|
389
|
-
const
|
|
390
|
-
|
|
379
|
+
const { runner } = makeGateRunner({
|
|
380
|
+
resolve: vi
|
|
391
381
|
.fn()
|
|
392
382
|
.mockReturnValue(
|
|
393
383
|
makeCheckResult({ state: "ask", matchedPattern: "*" }),
|
|
@@ -398,11 +388,10 @@ describe("runGateCheck", () => {
|
|
|
398
388
|
kind: "tool",
|
|
399
389
|
check: makeCheckResult({ state: "ask", matchedPattern: "*" }),
|
|
400
390
|
};
|
|
401
|
-
const result = await
|
|
391
|
+
const result = await runner.run(
|
|
402
392
|
makeDenialContextDescriptor(ctx),
|
|
403
393
|
null,
|
|
404
394
|
"tc-1",
|
|
405
|
-
deps,
|
|
406
395
|
);
|
|
407
396
|
expect(result.action).toBe("block");
|
|
408
397
|
if (result.action === "block") {
|
|
@@ -412,8 +401,8 @@ describe("runGateCheck", () => {
|
|
|
412
401
|
});
|
|
413
402
|
|
|
414
403
|
it("uses denialContext to format userDeniedReason with extension tag", async () => {
|
|
415
|
-
const
|
|
416
|
-
|
|
404
|
+
const { runner } = makeGateRunner({
|
|
405
|
+
resolve: vi
|
|
417
406
|
.fn()
|
|
418
407
|
.mockReturnValue(
|
|
419
408
|
makeCheckResult({ state: "ask", matchedPattern: "*" }),
|
|
@@ -428,11 +417,10 @@ describe("runGateCheck", () => {
|
|
|
428
417
|
kind: "tool",
|
|
429
418
|
check: makeCheckResult({ state: "ask", matchedPattern: "*" }),
|
|
430
419
|
};
|
|
431
|
-
const result = await
|
|
420
|
+
const result = await runner.run(
|
|
432
421
|
makeDenialContextDescriptor(ctx),
|
|
433
422
|
null,
|
|
434
423
|
"tc-1",
|
|
435
|
-
deps,
|
|
436
424
|
);
|
|
437
425
|
expect(result.action).toBe("block");
|
|
438
426
|
if (result.action === "block") {
|
|
@@ -442,3 +430,72 @@ describe("runGateCheck", () => {
|
|
|
442
430
|
});
|
|
443
431
|
});
|
|
444
432
|
});
|
|
433
|
+
|
|
434
|
+
// ── GateRunner.run — null and bypass dispatch ──────────────────────────────
|
|
435
|
+
|
|
436
|
+
describe("GateRunner.run — null and bypass dispatch", () => {
|
|
437
|
+
it("returns allow for a null gate", async () => {
|
|
438
|
+
const { runner, deps } = makeGateRunner();
|
|
439
|
+
const result = await runner.run(null, null, "tc-1");
|
|
440
|
+
expect(result).toEqual({ action: "allow" });
|
|
441
|
+
expect(deps.reporter.writeReviewLog).not.toHaveBeenCalled();
|
|
442
|
+
expect(deps.reporter.emitDecision).not.toHaveBeenCalled();
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it("returns allow for a bypass with no log or decision", async () => {
|
|
446
|
+
const { runner, deps } = makeGateRunner();
|
|
447
|
+
const bypass: GateBypass = { action: "allow" };
|
|
448
|
+
const result = await runner.run(bypass, null, "tc-1");
|
|
449
|
+
expect(result).toEqual({ action: "allow" });
|
|
450
|
+
expect(deps.reporter.writeReviewLog).not.toHaveBeenCalled();
|
|
451
|
+
expect(deps.reporter.emitDecision).not.toHaveBeenCalled();
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it("fires writeReviewLog for a bypass with a log entry", async () => {
|
|
455
|
+
const { runner, deps } = makeGateRunner();
|
|
456
|
+
const bypass: GateBypass = {
|
|
457
|
+
action: "allow",
|
|
458
|
+
log: { event: "infra.bypass", details: { path: "/x" } },
|
|
459
|
+
};
|
|
460
|
+
await runner.run(bypass, null, "tc-1");
|
|
461
|
+
expect(deps.reporter.writeReviewLog).toHaveBeenCalledWith("infra.bypass", {
|
|
462
|
+
path: "/x",
|
|
463
|
+
});
|
|
464
|
+
expect(deps.reporter.emitDecision).not.toHaveBeenCalled();
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it("fires emitDecision for a bypass with a decision", async () => {
|
|
468
|
+
const { runner, deps } = makeGateRunner();
|
|
469
|
+
const decision = {
|
|
470
|
+
surface: "path",
|
|
471
|
+
value: "/x",
|
|
472
|
+
result: "allow" as const,
|
|
473
|
+
resolution: "policy_allow" as const,
|
|
474
|
+
origin: null,
|
|
475
|
+
agentName: null,
|
|
476
|
+
matchedPattern: null,
|
|
477
|
+
};
|
|
478
|
+
const bypass: GateBypass = { action: "allow", decision };
|
|
479
|
+
await runner.run(bypass, null, "tc-1");
|
|
480
|
+
expect(deps.reporter.emitDecision).toHaveBeenCalledWith(decision);
|
|
481
|
+
expect(deps.reporter.writeReviewLog).not.toHaveBeenCalled();
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it("routes a descriptor to the gate check logic and returns allow", async () => {
|
|
485
|
+
const { runner } = makeGateRunner();
|
|
486
|
+
const result = await runner.run(makeDescriptor(), null, "tc-1");
|
|
487
|
+
expect(result).toEqual({ action: "allow" });
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it("routes a descriptor to the gate check logic and returns block", async () => {
|
|
491
|
+
const { runner } = makeGateRunner({
|
|
492
|
+
resolve: vi
|
|
493
|
+
.fn()
|
|
494
|
+
.mockReturnValue(
|
|
495
|
+
makeCheckResult({ state: "deny", matchedPattern: "*" }),
|
|
496
|
+
),
|
|
497
|
+
});
|
|
498
|
+
const result = await runner.run(makeDescriptor(), null, "tc-1");
|
|
499
|
+
expect(result).toMatchObject({ action: "block" });
|
|
500
|
+
});
|
|
501
|
+
});
|