@gotgenes/pi-permission-system 4.8.0 → 5.0.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.
@@ -1,4 +1,5 @@
1
1
  import { describe, expect, test } from "vitest";
2
+ import type { RuleOrigin } from "../src/rule";
2
3
  import { evaluate } from "../src/rule";
3
4
  import {
4
5
  composeRuleset,
@@ -9,7 +10,7 @@ import {
9
10
  // ── synthesizeDefaults ─────────────────────────────────────────────────────
10
11
 
11
12
  describe("synthesizeDefaults", () => {
12
- test("emits a single universal catch-all rule with layer 'default'", () => {
13
+ test("emits a single universal catch-all rule with layer 'default' and origin 'builtin'", () => {
13
14
  const rules = synthesizeDefaults("ask");
14
15
  expect(rules).toHaveLength(1);
15
16
  expect(rules[0]).toEqual({
@@ -17,6 +18,7 @@ describe("synthesizeDefaults", () => {
17
18
  pattern: "*",
18
19
  action: "ask",
19
20
  layer: "default",
21
+ origin: "builtin",
20
22
  });
21
23
  });
22
24
 
@@ -38,6 +40,39 @@ describe("synthesizeDefaults", () => {
38
40
  const rules = synthesizeDefaults("allow");
39
41
  expect(evaluate("read", "*", rules).layer).toBe("default");
40
42
  });
43
+
44
+ test("defaults to origin 'builtin' when no origin supplied", () => {
45
+ const rules = synthesizeDefaults("ask");
46
+ expect(rules[0].origin).toBe("builtin");
47
+ });
48
+
49
+ test("universal rule carries config scope origin when supplied", () => {
50
+ const origin: RuleOrigin = "global";
51
+ const rules = synthesizeDefaults("ask", origin);
52
+ expect(rules[0].origin).toBe("global");
53
+ });
54
+
55
+ test("origin is preserved through evaluate()", () => {
56
+ const rules = synthesizeDefaults("allow", "project");
57
+ const result = evaluate("read", "*", rules);
58
+ expect(result.origin).toBe("project");
59
+ });
60
+
61
+ test("all RuleOrigin values are accepted", () => {
62
+ const origins: RuleOrigin[] = [
63
+ "global",
64
+ "project",
65
+ "agent",
66
+ "project-agent",
67
+ "builtin",
68
+ "baseline",
69
+ "session",
70
+ ];
71
+ for (const origin of origins) {
72
+ const rules = synthesizeDefaults("ask", origin);
73
+ expect(rules[0].origin).toBe(origin);
74
+ }
75
+ });
41
76
  });
42
77
 
43
78
  // ── synthesizeBaseline ─────────────────────────────────────────────────────
@@ -50,6 +85,7 @@ describe("synthesizeBaseline", () => {
50
85
  pattern: "*",
51
86
  action: "deny" as const,
52
87
  layer: "config" as const,
88
+ origin: "global" as const,
53
89
  },
54
90
  ];
55
91
  expect(synthesizeBaseline(configRules)).toEqual([]);
@@ -66,19 +102,21 @@ describe("synthesizeBaseline", () => {
66
102
  pattern: "exa:*",
67
103
  action: "allow" as const,
68
104
  layer: "config" as const,
105
+ origin: "global" as const,
69
106
  },
70
107
  ];
71
108
  const rules = synthesizeBaseline(configRules);
72
109
  expect(rules).toHaveLength(5);
73
110
  });
74
111
 
75
- test("baseline rules all have layer 'baseline' and action 'allow'", () => {
112
+ test("baseline rules all have layer 'baseline', action 'allow', and origin 'baseline'", () => {
76
113
  const configRules = [
77
114
  {
78
115
  surface: "mcp",
79
116
  pattern: "exa:*",
80
117
  action: "allow" as const,
81
118
  layer: "config" as const,
119
+ origin: "global" as const,
82
120
  },
83
121
  ];
84
122
  const rules = synthesizeBaseline(configRules);
@@ -86,6 +124,7 @@ describe("synthesizeBaseline", () => {
86
124
  expect(rule.layer).toBe("baseline");
87
125
  expect(rule.action).toBe("allow");
88
126
  expect(rule.surface).toBe("mcp");
127
+ expect(rule.origin).toBe("baseline");
89
128
  }
90
129
  });
91
130
 
@@ -96,6 +135,7 @@ describe("synthesizeBaseline", () => {
96
135
  pattern: "exa:*",
97
136
  action: "allow" as const,
98
137
  layer: "config" as const,
138
+ origin: "global" as const,
99
139
  },
100
140
  ];
101
141
  const rules = synthesizeBaseline(configRules);
@@ -114,6 +154,7 @@ describe("synthesizeBaseline", () => {
114
154
  pattern: "git *",
115
155
  action: "allow" as const,
116
156
  layer: "config" as const,
157
+ origin: "global" as const,
117
158
  },
118
159
  ];
119
160
  expect(synthesizeBaseline(configRules)).toEqual([]);
@@ -126,12 +167,14 @@ describe("synthesizeBaseline", () => {
126
167
  pattern: "exa:*",
127
168
  action: "allow" as const,
128
169
  layer: "config" as const,
170
+ origin: "global" as const,
129
171
  },
130
172
  ];
131
173
  const rules = synthesizeBaseline(configRules);
132
174
  const result = evaluate("mcp", "mcp_status", rules);
133
175
  expect(result.action).toBe("allow");
134
176
  expect(result.layer).toBe("baseline");
177
+ expect(result.origin).toBe("baseline");
135
178
  });
136
179
  });
137
180
 
@@ -141,10 +184,21 @@ describe("composeRuleset", () => {
141
184
  test("returns concatenation of all layers in order", () => {
142
185
  const defaults = synthesizeDefaults("ask");
143
186
  const baseline = synthesizeBaseline([
144
- { surface: "mcp", pattern: "exa:*", action: "allow", layer: "config" },
187
+ {
188
+ surface: "mcp",
189
+ pattern: "exa:*",
190
+ action: "allow",
191
+ layer: "config",
192
+ origin: "global" as const,
193
+ },
145
194
  ]);
146
195
  const config = [
147
- { surface: "bash", pattern: "rm -rf *", action: "deny" as const },
196
+ {
197
+ surface: "bash",
198
+ pattern: "rm -rf *",
199
+ action: "deny" as const,
200
+ origin: "global" as const,
201
+ },
148
202
  ];
149
203
  const composed = composeRuleset(defaults, baseline, config);
150
204
  expect(composed.length).toBe(
@@ -160,6 +214,7 @@ describe("composeRuleset", () => {
160
214
  pattern: "*",
161
215
  action: "deny" as const,
162
216
  layer: "config" as const,
217
+ origin: "global" as const,
163
218
  },
164
219
  ];
165
220
  const composed = composeRuleset(defaults, [], config);
@@ -176,6 +231,7 @@ describe("composeRuleset", () => {
176
231
  pattern: "*",
177
232
  action: "allow" as const,
178
233
  layer: "config" as const,
234
+ origin: "global" as const,
179
235
  },
180
236
  ];
181
237
  const composed = composeRuleset(defaults, [], config);
@@ -192,6 +248,7 @@ describe("composeRuleset", () => {
192
248
  pattern: "mcp_status",
193
249
  action: "allow" as const,
194
250
  layer: "baseline" as const,
251
+ origin: "baseline" as const,
195
252
  },
196
253
  ];
197
254
  const config = [
@@ -200,6 +257,7 @@ describe("composeRuleset", () => {
200
257
  pattern: "mcp_status",
201
258
  action: "deny" as const,
202
259
  layer: "config" as const,
260
+ origin: "global" as const,
203
261
  },
204
262
  ];
205
263
  const composed = composeRuleset(defaults, baseline, config);
@@ -216,6 +274,7 @@ describe("composeRuleset", () => {
216
274
  pattern: "mcp_status",
217
275
  action: "allow" as const,
218
276
  layer: "baseline" as const,
277
+ origin: "baseline" as const,
219
278
  },
220
279
  ];
221
280
  const config = [
@@ -224,6 +283,7 @@ describe("composeRuleset", () => {
224
283
  pattern: "exa_web_search",
225
284
  action: "allow" as const,
226
285
  layer: "config" as const,
286
+ origin: "global" as const,
227
287
  },
228
288
  ];
229
289
  const composed = composeRuleset(defaults, baseline, config);
@@ -365,6 +365,7 @@ describe("getToolInputPreviewForLog", () => {
365
365
  toolName: "bash",
366
366
  state: "allow",
367
367
  source: "tool",
368
+ origin: "builtin",
368
369
  };
369
370
  expect(
370
371
  getToolInputPreviewForLog(result, { command: "ls" }, pathBearingTools),
@@ -376,6 +377,7 @@ describe("getToolInputPreviewForLog", () => {
376
377
  toolName: "mcp",
377
378
  state: "allow",
378
379
  source: "tool",
380
+ origin: "builtin",
379
381
  };
380
382
  expect(
381
383
  getToolInputPreviewForLog(result, {}, pathBearingTools),
@@ -387,6 +389,7 @@ describe("getToolInputPreviewForLog", () => {
387
389
  toolName: "some-server:some-tool",
388
390
  state: "allow",
389
391
  source: "mcp",
392
+ origin: "builtin",
390
393
  };
391
394
  expect(
392
395
  getToolInputPreviewForLog(result, {}, pathBearingTools),
@@ -398,6 +401,7 @@ describe("getToolInputPreviewForLog", () => {
398
401
  toolName: "read",
399
402
  state: "allow",
400
403
  source: "tool",
404
+ origin: "builtin",
401
405
  };
402
406
  const preview = getToolInputPreviewForLog(
403
407
  result,
@@ -413,6 +417,7 @@ describe("getToolInputPreviewForLog", () => {
413
417
  toolName: "task",
414
418
  state: "allow",
415
419
  source: "tool",
420
+ origin: "builtin",
416
421
  };
417
422
  const preview = getToolInputPreviewForLog(
418
423
  result,
@@ -431,6 +436,7 @@ describe("getPermissionLogContext", () => {
431
436
  toolName: "bash",
432
437
  state: "allow",
433
438
  source: "tool",
439
+ origin: "builtin",
434
440
  command: "ls -la",
435
441
  };
436
442
  const ctx = getPermissionLogContext(result, {}, pathBearingTools);
@@ -444,6 +450,7 @@ describe("getPermissionLogContext", () => {
444
450
  toolName: "read",
445
451
  state: "allow",
446
452
  source: "tool",
453
+ origin: "builtin",
447
454
  };
448
455
  const ctx = getPermissionLogContext(
449
456
  result,
@@ -452,4 +459,26 @@ describe("getPermissionLogContext", () => {
452
459
  );
453
460
  expect(ctx.toolInputPreview).toContain("/foo.ts");
454
461
  });
462
+
463
+ test("includes origin from check result when present", () => {
464
+ const result: PermissionCheckResult = {
465
+ toolName: "read",
466
+ state: "allow",
467
+ source: "tool",
468
+ origin: "project",
469
+ };
470
+ const ctx = getPermissionLogContext(result, {}, pathBearingTools);
471
+ expect(ctx.origin).toBe("project");
472
+ });
473
+
474
+ test("origin is 'builtin' when check result has builtin origin", () => {
475
+ const result: PermissionCheckResult = {
476
+ toolName: "read",
477
+ state: "allow",
478
+ source: "tool",
479
+ origin: "builtin",
480
+ };
481
+ const ctx = getPermissionLogContext(result, {}, pathBearingTools);
482
+ expect(ctx.origin).toBe("builtin");
483
+ });
455
484
  });