@gotgenes/pi-permission-system 8.2.0 → 8.2.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,99 +1,28 @@
1
1
  /**
2
2
  * Tests that handleInput emits permissions:decision events for skill input gates.
3
3
  */
4
- import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
5
4
  import { describe, expect, it, vi } from "vitest";
6
5
 
7
- import { PermissionGateHandler } from "#src/handlers/permission-gate-handler";
8
- import type { PermissionDecisionEvent } from "#src/permission-events";
9
- import { PERMISSIONS_DECISION_CHANNEL } from "#src/permission-events";
10
- import type { PermissionSession } from "#src/permission-session";
11
- import type { ToolRegistry } from "#src/tool-registry";
6
+ import {
7
+ getDecisionEvents,
8
+ makeCheckResult,
9
+ makeCtx,
10
+ makeHandler,
11
+ } from "#test/helpers/handler-fixtures";
12
12
 
13
13
  // ── helpers ────────────────────────────────────────────────────────────────
14
14
 
15
- function makeEvents() {
16
- return {
17
- emit: vi.fn(),
18
- on: vi.fn().mockReturnValue(() => undefined),
19
- };
20
- }
21
-
22
- function makeCtx(overrides: Partial<ExtensionContext> = {}): ExtensionContext {
23
- return {
24
- cwd: "/test/project",
25
- hasUI: true,
26
- ui: {
27
- setStatus: vi.fn(),
28
- notify: vi.fn(),
29
- select: vi.fn(),
30
- input: vi.fn(),
31
- },
32
- sessionManager: {
33
- getEntries: vi.fn().mockReturnValue([]),
34
- getSessionDir: vi.fn().mockReturnValue("/sessions/test"),
35
- addEntry: vi.fn(),
36
- },
37
- ...overrides,
38
- } as unknown as ExtensionContext;
39
- }
40
-
41
- function makeSession(
42
- state: "allow" | "deny" | "ask" = "allow",
43
- overrides: Partial<Record<keyof PermissionSession, unknown>> = {},
44
- ): PermissionSession {
45
- return {
46
- logger: { debug: vi.fn(), review: vi.fn(), warn: vi.fn() },
47
- activate: vi.fn(),
48
- resolveAgentName: vi.fn().mockReturnValue(null),
49
- checkPermission: vi.fn().mockReturnValue({
15
+ /** Build a checkPermission mock returning a skill-surface result. */
16
+ function makeSkillCheckPermission(state: "allow" | "deny" | "ask") {
17
+ return vi.fn().mockReturnValue(
18
+ makeCheckResult({
50
19
  state,
51
20
  toolName: "skill",
52
21
  source: "skill",
53
22
  origin: "global",
54
23
  matchedPattern: "*",
55
24
  }),
56
- getToolPermission: vi.fn().mockReturnValue("allow"),
57
- getSessionRuleset: vi.fn().mockReturnValue([]),
58
- recordSessionApproval: vi.fn(),
59
- canPrompt: vi.fn().mockReturnValue(true),
60
- prompt: vi.fn().mockResolvedValue({ approved: true, state: "approved" }),
61
- createPermissionRequestId: vi.fn().mockReturnValue("req-id"),
62
- ...overrides,
63
- } as unknown as PermissionSession;
64
- }
65
-
66
- function makeToolRegistry(): ToolRegistry {
67
- return {
68
- getAll: vi.fn().mockReturnValue([]),
69
- setActive: vi.fn(),
70
- };
71
- }
72
-
73
- function makeHandler(
74
- state: "allow" | "deny" | "ask" = "allow",
75
- sessionOverrides: Partial<Record<keyof PermissionSession, unknown>> = {},
76
- ): {
77
- handler: PermissionGateHandler;
78
- events: ReturnType<typeof makeEvents>;
79
- } {
80
- const session = makeSession(state, sessionOverrides);
81
- const events = makeEvents();
82
- const handler = new PermissionGateHandler(
83
- session,
84
- events,
85
- makeToolRegistry(),
86
25
  );
87
- return { handler, events };
88
- }
89
-
90
- /** Extract all permissions:decision payloads from the events.emit mock. */
91
- function getDecisionEvents(
92
- events: ReturnType<typeof makeEvents>,
93
- ): PermissionDecisionEvent[] {
94
- return events.emit.mock.calls
95
- .filter(([channel]) => channel === PERMISSIONS_DECISION_CHANNEL)
96
- .map(([, payload]) => payload as PermissionDecisionEvent);
97
26
  }
98
27
 
99
28
  // ── tests ──────────────────────────────────────────────────────────────────
@@ -106,7 +35,9 @@ describe("handleInput decision events — skill gate", () => {
106
35
  });
107
36
 
108
37
  it("emits allow with policy_allow for an allowed skill", async () => {
109
- const { handler, events } = makeHandler("allow");
38
+ const { handler, events } = makeHandler({
39
+ session: { checkPermission: makeSkillCheckPermission("allow") },
40
+ });
110
41
  await handler.handleInput({ text: "/skill:librarian" }, makeCtx());
111
42
 
112
43
  const decisions = getDecisionEvents(events);
@@ -120,7 +51,9 @@ describe("handleInput decision events — skill gate", () => {
120
51
  });
121
52
 
122
53
  it("emits deny with policy_deny for a denied skill", async () => {
123
- const { handler, events } = makeHandler("deny");
54
+ const { handler, events } = makeHandler({
55
+ session: { checkPermission: makeSkillCheckPermission("deny") },
56
+ });
124
57
  await handler.handleInput({ text: "/skill:restricted" }, makeCtx());
125
58
 
126
59
  const decisions = getDecisionEvents(events);
@@ -134,8 +67,13 @@ describe("handleInput decision events — skill gate", () => {
134
67
  });
135
68
 
136
69
  it("emits allow with user_approved when state=ask and user approves", async () => {
137
- const { handler, events } = makeHandler("ask", {
138
- prompt: vi.fn().mockResolvedValue({ approved: true, state: "approved" }),
70
+ const { handler, events } = makeHandler({
71
+ session: {
72
+ checkPermission: makeSkillCheckPermission("ask"),
73
+ prompt: vi
74
+ .fn()
75
+ .mockResolvedValue({ approved: true, state: "approved" }),
76
+ },
139
77
  });
140
78
  await handler.handleInput({ text: "/skill:explorer" }, makeCtx());
141
79
 
@@ -150,8 +88,11 @@ describe("handleInput decision events — skill gate", () => {
150
88
  });
151
89
 
152
90
  it("emits deny with user_denied when state=ask and user denies", async () => {
153
- const { handler, events } = makeHandler("ask", {
154
- prompt: vi.fn().mockResolvedValue({ approved: false, state: "denied" }),
91
+ const { handler, events } = makeHandler({
92
+ session: {
93
+ checkPermission: makeSkillCheckPermission("ask"),
94
+ prompt: vi.fn().mockResolvedValue({ approved: false, state: "denied" }),
95
+ },
155
96
  });
156
97
  await handler.handleInput({ text: "/skill:explorer" }, makeCtx());
157
98
 
@@ -166,8 +107,11 @@ describe("handleInput decision events — skill gate", () => {
166
107
  });
167
108
 
168
109
  it("emits deny with confirmation_unavailable when state=ask but no UI", async () => {
169
- const { handler, events } = makeHandler("ask", {
170
- canPrompt: vi.fn().mockReturnValue(false),
110
+ const { handler, events } = makeHandler({
111
+ session: {
112
+ checkPermission: makeSkillCheckPermission("ask"),
113
+ canPrompt: vi.fn().mockReturnValue(false),
114
+ },
171
115
  });
172
116
  await handler.handleInput(
173
117
  { text: "/skill:explorer" },
@@ -185,12 +129,15 @@ describe("handleInput decision events — skill gate", () => {
185
129
  });
186
130
 
187
131
  it("emits allow with auto_approved when prompt returns autoApproved:true", async () => {
188
- const { handler, events } = makeHandler("ask", {
189
- prompt: vi.fn().mockResolvedValue({
190
- approved: true,
191
- state: "approved",
192
- autoApproved: true,
193
- }),
132
+ const { handler, events } = makeHandler({
133
+ session: {
134
+ checkPermission: makeSkillCheckPermission("ask"),
135
+ prompt: vi.fn().mockResolvedValue({
136
+ approved: true,
137
+ state: "approved",
138
+ autoApproved: true,
139
+ }),
140
+ },
194
141
  });
195
142
  await handler.handleInput({ text: "/skill:explorer" }, makeCtx());
196
143
 
@@ -1,83 +1,15 @@
1
- import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
1
  import { describe, expect, it, vi } from "vitest";
3
2
 
4
- import {
5
- extractSkillNameFromInput,
6
- PermissionGateHandler,
7
- } from "#src/handlers/permission-gate-handler";
8
- import type { PermissionSession } from "#src/permission-session";
9
- import type { ToolRegistry } from "#src/tool-registry";
3
+ import { extractSkillNameFromInput } from "#src/handlers/permission-gate-handler";
10
4
 
11
- // ── helpers ────────────────────────────────────────────────────────────────
5
+ import { makeCtx, makeHandler } from "#test/helpers/handler-fixtures";
12
6
 
13
- function makeCtx(overrides: Partial<ExtensionContext> = {}): ExtensionContext {
14
- return {
15
- cwd: "/test/project",
16
- hasUI: true,
17
- ui: {
18
- setStatus: vi.fn(),
19
- notify: vi.fn(),
20
- select: vi.fn(),
21
- input: vi.fn(),
22
- },
23
- sessionManager: {
24
- getEntries: vi.fn().mockReturnValue([]),
25
- getSessionDir: vi.fn().mockReturnValue("/sessions/test"),
26
- addEntry: vi.fn(),
27
- },
28
- ...overrides,
29
- } as unknown as ExtensionContext;
30
- }
7
+ // ── helpers ────────────────────────────────────────────────────────────────
31
8
 
32
9
  function makeInputEvent(text: string) {
33
10
  return { text };
34
11
  }
35
12
 
36
- function makeSession(
37
- overrides: Partial<Record<keyof PermissionSession, unknown>> = {},
38
- ): PermissionSession {
39
- return {
40
- logger: { debug: vi.fn(), review: vi.fn(), warn: vi.fn() },
41
- activate: vi.fn(),
42
- resolveAgentName: vi.fn().mockReturnValue(null),
43
- checkPermission: vi.fn().mockReturnValue({ state: "allow" }),
44
- getToolPermission: vi.fn().mockReturnValue("allow"),
45
- getSessionRuleset: vi.fn().mockReturnValue([]),
46
- recordSessionApproval: vi.fn(),
47
- canPrompt: vi.fn().mockReturnValue(true),
48
- prompt: vi.fn().mockResolvedValue({ approved: true, state: "approved" }),
49
- createPermissionRequestId: vi.fn().mockReturnValue("req-id"),
50
- ...overrides,
51
- } as unknown as PermissionSession;
52
- }
53
-
54
- function makeEvents() {
55
- return {
56
- emit: vi.fn(),
57
- on: vi.fn().mockReturnValue(() => undefined),
58
- };
59
- }
60
-
61
- function makeToolRegistry(): ToolRegistry {
62
- return {
63
- getAll: vi.fn().mockReturnValue([]),
64
- setActive: vi.fn(),
65
- };
66
- }
67
-
68
- function makeHandler(overrides?: {
69
- session?: Partial<Record<keyof PermissionSession, unknown>>;
70
- }): {
71
- handler: PermissionGateHandler;
72
- session: PermissionSession;
73
- } {
74
- const session = makeSession(overrides?.session);
75
- const events = makeEvents();
76
- const toolRegistry = makeToolRegistry();
77
- const handler = new PermissionGateHandler(session, events, toolRegistry);
78
- return { handler, session };
79
- }
80
-
81
13
  // ── extractSkillNameFromInput ──────────────────────────────────────────────
82
14
 
83
15
  describe("extractSkillNameFromInput", () => {
@@ -1,8 +1,10 @@
1
- import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
1
  import { describe, expect, it, vi } from "vitest";
2
+
3
3
  import { SessionLifecycleHandler } from "#src/handlers/lifecycle";
4
4
  import type { PermissionSession } from "#src/permission-session";
5
5
 
6
+ import { makeCtx } from "#test/helpers/handler-fixtures";
7
+
6
8
  // ── status stub ────────────────────────────────────────────────────────────
7
9
  vi.mock("../../src/status", () => ({
8
10
  PERMISSION_SYSTEM_STATUS_KEY: "permission-system",
@@ -12,25 +14,6 @@ vi.mock("../../src/status", () => ({
12
14
 
13
15
  // ── helpers ────────────────────────────────────────────────────────────────
14
16
 
15
- function makeCtx(overrides: Partial<ExtensionContext> = {}): ExtensionContext {
16
- return {
17
- cwd: "/test/project",
18
- hasUI: true,
19
- ui: {
20
- setStatus: vi.fn(),
21
- notify: vi.fn(),
22
- select: vi.fn(),
23
- input: vi.fn(),
24
- },
25
- sessionManager: {
26
- getEntries: vi.fn().mockReturnValue([]),
27
- getSessionDir: vi.fn().mockReturnValue("/sessions/test"),
28
- addEntry: vi.fn(),
29
- },
30
- ...overrides,
31
- } as unknown as ExtensionContext;
32
- }
33
-
34
17
  function makeSession(
35
18
  overrides: Partial<Record<keyof PermissionSession, unknown>> = {},
36
19
  ): PermissionSession {
@@ -2,125 +2,15 @@
2
2
  * Tests that handleToolCall emits permissions:decision events at every
3
3
  * gate resolution and fast-path site.
4
4
  */
5
- import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
6
5
  import { describe, expect, it, vi } from "vitest";
7
- import { DEFAULT_EXTENSION_CONFIG } from "#src/extension-config";
8
- import { PermissionGateHandler } from "#src/handlers/permission-gate-handler";
9
- import type { PermissionDecisionEvent } from "#src/permission-events";
10
- import { PERMISSIONS_DECISION_CHANNEL } from "#src/permission-events";
11
- import type { PermissionSession } from "#src/permission-session";
12
- import type { ToolRegistry } from "#src/tool-registry";
13
- import type { PermissionCheckResult } from "#src/types";
14
-
15
- // ── helpers ────────────────────────────────────────────────────────────────
16
-
17
- function makeEvents() {
18
- return {
19
- emit: vi.fn(),
20
- on: vi.fn().mockReturnValue(() => undefined),
21
- };
22
- }
23
-
24
- function makeCtx(overrides: Partial<ExtensionContext> = {}): ExtensionContext {
25
- return {
26
- cwd: "/test/project",
27
- hasUI: true,
28
- ui: {
29
- setStatus: vi.fn(),
30
- notify: vi.fn(),
31
- select: vi.fn(),
32
- input: vi.fn(),
33
- },
34
- sessionManager: {
35
- getEntries: vi.fn().mockReturnValue([]),
36
- getSessionDir: vi.fn().mockReturnValue("/sessions/test"),
37
- addEntry: vi.fn(),
38
- },
39
- ...overrides,
40
- } as unknown as ExtensionContext;
41
- }
42
-
43
- function makeToolCallEvent(
44
- toolName: string,
45
- extraFields: Record<string, unknown> = {},
46
- ) {
47
- return {
48
- type: "tool_call",
49
- toolCallId: "tc-1",
50
- name: toolName,
51
- input: {},
52
- ...extraFields,
53
- };
54
- }
55
-
56
- function makeCheckResult(
57
- state: "allow" | "deny" | "ask",
58
- overrides: Partial<PermissionCheckResult> = {},
59
- ): PermissionCheckResult {
60
- return {
61
- state,
62
- toolName: "read",
63
- source: "tool",
64
- origin: "builtin",
65
- matchedPattern: "*",
66
- ...overrides,
67
- };
68
- }
69
-
70
- function makeSession(
71
- overrides: Partial<Record<keyof PermissionSession, unknown>> = {},
72
- ): PermissionSession {
73
- return {
74
- logger: { debug: vi.fn(), review: vi.fn(), warn: vi.fn() },
75
- activate: vi.fn(),
76
- resolveAgentName: vi.fn().mockReturnValue(null),
77
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("allow")),
78
- getToolPermission: vi.fn().mockReturnValue("allow"),
79
- getSessionRuleset: vi.fn().mockReturnValue([]),
80
- recordSessionApproval: vi.fn(),
81
- getActiveSkillEntries: vi.fn().mockReturnValue([]),
82
- getInfrastructureDirs: vi
83
- .fn()
84
- .mockReturnValue(["/test/agent", "/test/agent/git"]),
85
- getInfrastructureReadPaths: vi.fn().mockReturnValue([]),
86
- config: DEFAULT_EXTENSION_CONFIG,
87
- canPrompt: vi.fn().mockReturnValue(true),
88
- prompt: vi.fn().mockResolvedValue({ approved: true, state: "approved" }),
89
- ...overrides,
90
- } as unknown as PermissionSession;
91
- }
92
-
93
- function makeToolRegistry(overrides: Partial<ToolRegistry> = {}): ToolRegistry {
94
- return {
95
- getAll: vi.fn().mockReturnValue([{ name: "read" }, { name: "bash" }]),
96
- setActive: vi.fn(),
97
- ...overrides,
98
- };
99
- }
100
-
101
- function makeHandler(overrides?: {
102
- session?: Partial<Record<keyof PermissionSession, unknown>>;
103
- toolRegistry?: Partial<ToolRegistry>;
104
- }): {
105
- handler: PermissionGateHandler;
106
- events: ReturnType<typeof makeEvents>;
107
- session: PermissionSession;
108
- } {
109
- const session = makeSession(overrides?.session);
110
- const events = makeEvents();
111
- const toolRegistry = makeToolRegistry(overrides?.toolRegistry);
112
- const handler = new PermissionGateHandler(session, events, toolRegistry);
113
- return { handler, events, session };
114
- }
115
-
116
- /** Extract all permissions:decision payloads from the events.emit mock. */
117
- function getDecisionEvents(
118
- events: ReturnType<typeof makeEvents>,
119
- ): PermissionDecisionEvent[] {
120
- return events.emit.mock.calls
121
- .filter(([channel]) => channel === PERMISSIONS_DECISION_CHANNEL)
122
- .map(([, payload]) => payload as PermissionDecisionEvent);
123
- }
6
+
7
+ import {
8
+ getDecisionEvents,
9
+ makeCheckResult,
10
+ makeCtx,
11
+ makeHandler,
12
+ makeToolCallEvent,
13
+ } from "#test/helpers/handler-fixtures";
124
14
 
125
15
  // ── policy_allow path ──────────────────────────────────────────────────────
126
16
 
@@ -129,7 +19,8 @@ describe("handleToolCall decision events — policy_allow", () => {
129
19
  const { handler, events } = makeHandler({
130
20
  session: {
131
21
  checkPermission: vi.fn().mockReturnValue(
132
- makeCheckResult("allow", {
22
+ makeCheckResult({
23
+ state: "allow",
133
24
  origin: "global",
134
25
  matchedPattern: "*",
135
26
  }),
@@ -158,7 +49,8 @@ describe("handleToolCall decision events — policy_deny", () => {
158
49
  const { handler, events } = makeHandler({
159
50
  session: {
160
51
  checkPermission: vi.fn().mockReturnValue(
161
- makeCheckResult("deny", {
52
+ makeCheckResult({
53
+ state: "deny",
162
54
  origin: "project",
163
55
  matchedPattern: "read",
164
56
  }),
@@ -185,7 +77,8 @@ describe("handleToolCall decision events — session_approved", () => {
185
77
  const { handler, events } = makeHandler({
186
78
  session: {
187
79
  checkPermission: vi.fn().mockReturnValue(
188
- makeCheckResult("allow", {
80
+ makeCheckResult({
81
+ state: "allow",
189
82
  source: "session",
190
83
  matchedPattern: "git *",
191
84
  }),
@@ -214,7 +107,9 @@ describe("handleToolCall decision events — user_approved", () => {
214
107
  it("emits allow with user_approved when state=ask and user approves once", async () => {
215
108
  const { handler, events } = makeHandler({
216
109
  session: {
217
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
110
+ checkPermission: vi
111
+ .fn()
112
+ .mockReturnValue(makeCheckResult({ state: "ask" })),
218
113
  prompt: vi
219
114
  .fn()
220
115
  .mockResolvedValue({ approved: true, state: "approved" }),
@@ -234,7 +129,9 @@ describe("handleToolCall decision events — user_approved", () => {
234
129
  it("emits allow with user_approved_for_session when user approves for session", async () => {
235
130
  const { handler, events } = makeHandler({
236
131
  session: {
237
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
132
+ checkPermission: vi
133
+ .fn()
134
+ .mockReturnValue(makeCheckResult({ state: "ask" })),
238
135
  prompt: vi.fn().mockResolvedValue({
239
136
  approved: true,
240
137
  state: "approved_for_session",
@@ -259,7 +156,9 @@ describe("handleToolCall decision events — user_denied", () => {
259
156
  it("emits deny with user_denied when state=ask and user denies", async () => {
260
157
  const { handler, events } = makeHandler({
261
158
  session: {
262
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
159
+ checkPermission: vi
160
+ .fn()
161
+ .mockReturnValue(makeCheckResult({ state: "ask" })),
263
162
  prompt: vi.fn().mockResolvedValue({ approved: false, state: "denied" }),
264
163
  },
265
164
  });
@@ -281,7 +180,9 @@ describe("handleToolCall decision events — confirmation_unavailable", () => {
281
180
  it("emits deny with confirmation_unavailable when state=ask but no UI", async () => {
282
181
  const { handler, events } = makeHandler({
283
182
  session: {
284
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
183
+ checkPermission: vi
184
+ .fn()
185
+ .mockReturnValue(makeCheckResult({ state: "ask" })),
285
186
  canPrompt: vi.fn().mockReturnValue(false),
286
187
  },
287
188
  });
@@ -307,7 +208,7 @@ describe("handleToolCall decision events — infrastructure_auto_allowed", () =>
307
208
  const infraDir = "/test/agent";
308
209
  const { handler, events } = makeHandler({
309
210
  session: {
310
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("allow")),
211
+ checkPermission: vi.fn().mockReturnValue(makeCheckResult()),
311
212
  getInfrastructureDirs: vi.fn().mockReturnValue([infraDir]),
312
213
  },
313
214
  });
@@ -335,7 +236,9 @@ describe("handleToolCall decision events — auto_approved", () => {
335
236
  it("emits allow with auto_approved when prompt returns autoApproved:true", async () => {
336
237
  const { handler, events } = makeHandler({
337
238
  session: {
338
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
239
+ checkPermission: vi
240
+ .fn()
241
+ .mockReturnValue(makeCheckResult({ state: "ask" })),
339
242
  prompt: vi.fn().mockResolvedValue({
340
243
  approved: true,
341
244
  state: "approved",