@enactprotocol/shared 1.2.13 → 2.0.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.
Files changed (207) hide show
  1. package/README.md +44 -0
  2. package/dist/config.d.ts +164 -0
  3. package/dist/config.d.ts.map +1 -0
  4. package/dist/config.js +386 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/constants.d.ts +15 -5
  7. package/dist/constants.d.ts.map +1 -0
  8. package/dist/constants.js +24 -8
  9. package/dist/constants.js.map +1 -0
  10. package/dist/execution/command.d.ts +102 -0
  11. package/dist/execution/command.d.ts.map +1 -0
  12. package/dist/execution/command.js +262 -0
  13. package/dist/execution/command.js.map +1 -0
  14. package/dist/execution/index.d.ts +12 -0
  15. package/dist/execution/index.d.ts.map +1 -0
  16. package/dist/execution/index.js +17 -0
  17. package/dist/execution/index.js.map +1 -0
  18. package/dist/execution/runtime.d.ts +82 -0
  19. package/dist/execution/runtime.d.ts.map +1 -0
  20. package/dist/execution/runtime.js +273 -0
  21. package/dist/execution/runtime.js.map +1 -0
  22. package/dist/execution/types.d.ts +306 -0
  23. package/dist/execution/types.d.ts.map +1 -0
  24. package/dist/execution/types.js +14 -0
  25. package/dist/execution/types.js.map +1 -0
  26. package/dist/execution/validation.d.ts +43 -0
  27. package/dist/execution/validation.d.ts.map +1 -0
  28. package/dist/execution/validation.js +430 -0
  29. package/dist/execution/validation.js.map +1 -0
  30. package/dist/index.d.ts +21 -21
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +49 -25
  33. package/dist/index.js.map +1 -0
  34. package/dist/manifest/index.d.ts +7 -0
  35. package/dist/manifest/index.d.ts.map +1 -0
  36. package/dist/manifest/index.js +10 -0
  37. package/dist/manifest/index.js.map +1 -0
  38. package/dist/manifest/loader.d.ts +76 -0
  39. package/dist/manifest/loader.d.ts.map +1 -0
  40. package/dist/manifest/loader.js +146 -0
  41. package/dist/manifest/loader.js.map +1 -0
  42. package/dist/manifest/parser.d.ts +64 -0
  43. package/dist/manifest/parser.d.ts.map +1 -0
  44. package/dist/manifest/parser.js +135 -0
  45. package/dist/manifest/parser.js.map +1 -0
  46. package/dist/manifest/validator.d.ts +95 -0
  47. package/dist/manifest/validator.d.ts.map +1 -0
  48. package/dist/manifest/validator.js +258 -0
  49. package/dist/manifest/validator.js.map +1 -0
  50. package/dist/paths.d.ts +57 -0
  51. package/dist/paths.d.ts.map +1 -0
  52. package/dist/paths.js +93 -0
  53. package/dist/paths.js.map +1 -0
  54. package/dist/registry.d.ts +73 -0
  55. package/dist/registry.d.ts.map +1 -0
  56. package/dist/registry.js +147 -0
  57. package/dist/registry.js.map +1 -0
  58. package/dist/resolver.d.ts +89 -0
  59. package/dist/resolver.d.ts.map +1 -0
  60. package/dist/resolver.js +282 -0
  61. package/dist/resolver.js.map +1 -0
  62. package/dist/types/index.d.ts +6 -0
  63. package/dist/types/index.d.ts.map +1 -0
  64. package/dist/types/index.js +5 -0
  65. package/dist/types/index.js.map +1 -0
  66. package/dist/types/manifest.d.ts +201 -0
  67. package/dist/types/manifest.d.ts.map +1 -0
  68. package/dist/types/manifest.js +13 -0
  69. package/dist/types/manifest.js.map +1 -0
  70. package/dist/types.d.ts +5 -132
  71. package/dist/types.d.ts.map +1 -0
  72. package/dist/types.js +5 -3
  73. package/dist/types.js.map +1 -0
  74. package/dist/utils/fs.d.ts +105 -0
  75. package/dist/utils/fs.d.ts.map +1 -0
  76. package/dist/utils/fs.js +233 -0
  77. package/dist/utils/fs.js.map +1 -0
  78. package/dist/utils/logger.d.ts +102 -25
  79. package/dist/utils/logger.d.ts.map +1 -0
  80. package/dist/utils/logger.js +214 -57
  81. package/dist/utils/logger.js.map +1 -0
  82. package/dist/utils/version.d.ts +60 -2
  83. package/dist/utils/version.d.ts.map +1 -0
  84. package/dist/utils/version.js +255 -31
  85. package/dist/utils/version.js.map +1 -0
  86. package/package.json +16 -58
  87. package/src/config.ts +510 -0
  88. package/src/constants.ts +36 -0
  89. package/src/execution/command.ts +314 -0
  90. package/src/execution/index.ts +73 -0
  91. package/src/execution/runtime.ts +308 -0
  92. package/src/execution/types.ts +379 -0
  93. package/src/execution/validation.ts +508 -0
  94. package/src/index.ts +238 -30
  95. package/src/manifest/index.ts +36 -0
  96. package/src/manifest/loader.ts +187 -0
  97. package/src/manifest/parser.ts +173 -0
  98. package/src/manifest/validator.ts +309 -0
  99. package/src/paths.ts +108 -0
  100. package/src/registry.ts +219 -0
  101. package/src/resolver.ts +345 -0
  102. package/src/types/index.ts +30 -0
  103. package/src/types/manifest.ts +255 -0
  104. package/src/types.ts +5 -188
  105. package/src/utils/fs.ts +281 -0
  106. package/src/utils/logger.ts +270 -59
  107. package/src/utils/version.ts +304 -36
  108. package/tests/config.test.ts +515 -0
  109. package/tests/execution/command.test.ts +317 -0
  110. package/tests/execution/validation.test.ts +384 -0
  111. package/tests/fixtures/invalid-tool.yaml +4 -0
  112. package/tests/fixtures/valid-tool.md +62 -0
  113. package/tests/fixtures/valid-tool.yaml +40 -0
  114. package/tests/index.test.ts +8 -0
  115. package/tests/manifest/loader.test.ts +291 -0
  116. package/tests/manifest/parser.test.ts +345 -0
  117. package/tests/manifest/validator.test.ts +394 -0
  118. package/tests/manifest-types.test.ts +358 -0
  119. package/tests/paths.test.ts +153 -0
  120. package/tests/registry.test.ts +231 -0
  121. package/tests/resolver.test.ts +272 -0
  122. package/tests/utils/fs.test.ts +388 -0
  123. package/tests/utils/logger.test.ts +480 -0
  124. package/tests/utils/version.test.ts +390 -0
  125. package/tsconfig.json +12 -0
  126. package/dist/LocalToolResolver.d.ts +0 -84
  127. package/dist/LocalToolResolver.js +0 -353
  128. package/dist/api/enact-api.d.ts +0 -130
  129. package/dist/api/enact-api.js +0 -428
  130. package/dist/api/index.d.ts +0 -2
  131. package/dist/api/index.js +0 -2
  132. package/dist/api/types.d.ts +0 -103
  133. package/dist/api/types.js +0 -1
  134. package/dist/core/DaggerExecutionProvider.d.ts +0 -169
  135. package/dist/core/DaggerExecutionProvider.js +0 -1029
  136. package/dist/core/DirectExecutionProvider.d.ts +0 -23
  137. package/dist/core/DirectExecutionProvider.js +0 -406
  138. package/dist/core/EnactCore.d.ts +0 -162
  139. package/dist/core/EnactCore.js +0 -597
  140. package/dist/core/NativeExecutionProvider.d.ts +0 -9
  141. package/dist/core/NativeExecutionProvider.js +0 -16
  142. package/dist/core/index.d.ts +0 -3
  143. package/dist/core/index.js +0 -3
  144. package/dist/exec/index.d.ts +0 -3
  145. package/dist/exec/index.js +0 -3
  146. package/dist/exec/logger.d.ts +0 -11
  147. package/dist/exec/logger.js +0 -57
  148. package/dist/exec/validate.d.ts +0 -5
  149. package/dist/exec/validate.js +0 -167
  150. package/dist/lib/enact-direct.d.ts +0 -150
  151. package/dist/lib/enact-direct.js +0 -159
  152. package/dist/lib/index.d.ts +0 -1
  153. package/dist/lib/index.js +0 -1
  154. package/dist/security/index.d.ts +0 -3
  155. package/dist/security/index.js +0 -3
  156. package/dist/security/security.d.ts +0 -23
  157. package/dist/security/security.js +0 -137
  158. package/dist/security/sign.d.ts +0 -103
  159. package/dist/security/sign.js +0 -666
  160. package/dist/security/verification-enforcer.d.ts +0 -53
  161. package/dist/security/verification-enforcer.js +0 -204
  162. package/dist/services/McpCoreService.d.ts +0 -98
  163. package/dist/services/McpCoreService.js +0 -124
  164. package/dist/services/index.d.ts +0 -1
  165. package/dist/services/index.js +0 -1
  166. package/dist/utils/config.d.ts +0 -111
  167. package/dist/utils/config.js +0 -342
  168. package/dist/utils/env-loader.d.ts +0 -54
  169. package/dist/utils/env-loader.js +0 -270
  170. package/dist/utils/help.d.ts +0 -36
  171. package/dist/utils/help.js +0 -248
  172. package/dist/utils/index.d.ts +0 -7
  173. package/dist/utils/index.js +0 -7
  174. package/dist/utils/silent-monitor.d.ts +0 -67
  175. package/dist/utils/silent-monitor.js +0 -242
  176. package/dist/utils/timeout.d.ts +0 -5
  177. package/dist/utils/timeout.js +0 -23
  178. package/dist/web/env-manager-server.d.ts +0 -29
  179. package/dist/web/env-manager-server.js +0 -367
  180. package/dist/web/index.d.ts +0 -1
  181. package/dist/web/index.js +0 -1
  182. package/src/LocalToolResolver.ts +0 -424
  183. package/src/api/enact-api.ts +0 -604
  184. package/src/api/index.ts +0 -2
  185. package/src/api/types.ts +0 -114
  186. package/src/core/DaggerExecutionProvider.ts +0 -1357
  187. package/src/core/DirectExecutionProvider.ts +0 -484
  188. package/src/core/EnactCore.ts +0 -847
  189. package/src/core/index.ts +0 -3
  190. package/src/exec/index.ts +0 -3
  191. package/src/exec/logger.ts +0 -63
  192. package/src/exec/validate.ts +0 -238
  193. package/src/lib/enact-direct.ts +0 -254
  194. package/src/lib/index.ts +0 -1
  195. package/src/services/McpCoreService.ts +0 -201
  196. package/src/services/index.ts +0 -1
  197. package/src/utils/config.ts +0 -438
  198. package/src/utils/env-loader.ts +0 -370
  199. package/src/utils/help.ts +0 -257
  200. package/src/utils/index.ts +0 -7
  201. package/src/utils/silent-monitor.ts +0 -328
  202. package/src/utils/timeout.ts +0 -26
  203. package/src/web/env-manager-server.ts +0 -465
  204. package/src/web/index.ts +0 -1
  205. package/src/web/static/app.js +0 -663
  206. package/src/web/static/index.html +0 -117
  207. package/src/web/static/style.css +0 -291
@@ -0,0 +1,515 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
2
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import yaml from "js-yaml";
6
+ import {
7
+ DEFAULT_CONFIG,
8
+ type EnactConfig,
9
+ // Legacy aliases for backwards compatibility
10
+ addTrustedAuditor,
11
+ addTrustedIdentity,
12
+ configExists,
13
+ emailToProviderIdentity,
14
+ getConfigValue,
15
+ getMinimumAttestations,
16
+ getTrustPolicy,
17
+ getTrustedAuditors,
18
+ getTrustedIdentities,
19
+ isAuditorTrusted,
20
+ isIdentityTrusted,
21
+ loadConfig,
22
+ removeTrustedAuditor,
23
+ removeTrustedIdentity,
24
+ resetConfig,
25
+ saveConfig,
26
+ setConfigValue,
27
+ } from "../src/config";
28
+
29
+ // Use a test-specific home directory to avoid affecting real config
30
+ const TEST_HOME = join(import.meta.dir, "fixtures", "config-test-home");
31
+
32
+ describe("configuration manager", () => {
33
+ beforeAll(() => {
34
+ // Mock homedir to use test directory
35
+ // We need to mock the module-level function
36
+ // For this test, we'll manipulate the files directly and test the logic
37
+ });
38
+
39
+ beforeEach(() => {
40
+ // Clean up test directory before each test
41
+ if (existsSync(TEST_HOME)) {
42
+ rmSync(TEST_HOME, { recursive: true, force: true });
43
+ }
44
+ });
45
+
46
+ afterAll(() => {
47
+ // Clean up test directory
48
+ if (existsSync(TEST_HOME)) {
49
+ rmSync(TEST_HOME, { recursive: true, force: true });
50
+ }
51
+ });
52
+
53
+ describe("DEFAULT_CONFIG", () => {
54
+ test("has all required top-level keys", () => {
55
+ expect(DEFAULT_CONFIG.version).toBeDefined();
56
+ expect(DEFAULT_CONFIG.trust).toBeDefined();
57
+ expect(DEFAULT_CONFIG.cache).toBeDefined();
58
+ expect(DEFAULT_CONFIG.execution).toBeDefined();
59
+ expect(DEFAULT_CONFIG.registry).toBeDefined();
60
+ });
61
+
62
+ test("has sensible default values", () => {
63
+ expect(DEFAULT_CONFIG.trust?.policy).toBe("prompt");
64
+ expect(DEFAULT_CONFIG.trust?.minimum_attestations).toBe(1);
65
+ expect(DEFAULT_CONFIG.cache?.maxSizeMb).toBe(1024);
66
+ expect(DEFAULT_CONFIG.execution?.defaultTimeout).toBe("30s");
67
+ expect(DEFAULT_CONFIG.registry?.url).toBe("https://enact.tools");
68
+ });
69
+ });
70
+
71
+ describe("loadConfig", () => {
72
+ test("returns default config if file does not exist", () => {
73
+ // The actual homedir doesn't have config, or we test with mock
74
+ const config = loadConfig();
75
+ expect(config).toBeDefined();
76
+ expect(config.version).toBeDefined();
77
+ });
78
+
79
+ test("returns default config structure", () => {
80
+ const config = loadConfig();
81
+ expect(config.trust).toBeDefined();
82
+ expect(config.cache).toBeDefined();
83
+ expect(config.execution).toBeDefined();
84
+ expect(config.registry).toBeDefined();
85
+ });
86
+ });
87
+
88
+ describe("saveConfig", () => {
89
+ test("creates ~/.enact/ directory if needed", () => {
90
+ const config: EnactConfig = { ...DEFAULT_CONFIG };
91
+
92
+ // This will create in the real home directory
93
+ // For integration test purposes
94
+ saveConfig(config);
95
+
96
+ // Verify config was saved
97
+ expect(configExists()).toBe(true);
98
+ });
99
+
100
+ test("writes valid YAML", () => {
101
+ const config: EnactConfig = {
102
+ version: "1.0.0",
103
+ trust: {
104
+ auditors: ["github:alice"],
105
+ policy: "require_attestation",
106
+ },
107
+ cache: {
108
+ maxSizeMb: 2048,
109
+ },
110
+ };
111
+
112
+ saveConfig(config);
113
+
114
+ // Reload and verify
115
+ const loaded = loadConfig();
116
+ expect(loaded.trust?.auditors).toContain("github:alice");
117
+ expect(loaded.trust?.policy).toBe("require_attestation");
118
+ expect(loaded.cache?.maxSizeMb).toBe(2048);
119
+ });
120
+
121
+ test("preserves all config fields", () => {
122
+ const config: EnactConfig = {
123
+ version: "2.0.0",
124
+ trust: {
125
+ auditors: ["github:aud1", "google:aud2"],
126
+ policy: "require_attestation",
127
+ minimum_attestations: 2,
128
+ },
129
+ cache: {
130
+ maxSizeMb: 512,
131
+ ttlSeconds: 3600,
132
+ },
133
+ execution: {
134
+ defaultTimeout: "1m",
135
+ verbose: true,
136
+ },
137
+ registry: {
138
+ url: "https://custom.registry.com",
139
+ authTokenRef: "token-ref",
140
+ },
141
+ };
142
+
143
+ saveConfig(config);
144
+ const loaded = loadConfig();
145
+
146
+ expect(loaded.version).toBe("2.0.0");
147
+ expect(loaded.trust?.auditors).toEqual(["github:aud1", "google:aud2"]);
148
+ expect(loaded.trust?.policy).toBe("require_attestation");
149
+ expect(loaded.trust?.minimum_attestations).toBe(2);
150
+ expect(loaded.cache?.maxSizeMb).toBe(512);
151
+ expect(loaded.cache?.ttlSeconds).toBe(3600);
152
+ expect(loaded.execution?.defaultTimeout).toBe("1m");
153
+ expect(loaded.execution?.verbose).toBe(true);
154
+ expect(loaded.registry?.url).toBe("https://custom.registry.com");
155
+ expect(loaded.registry?.authTokenRef).toBe("token-ref");
156
+ });
157
+ });
158
+
159
+ describe("getConfigValue", () => {
160
+ test("returns default for missing keys", () => {
161
+ const value = getConfigValue("nonexistent.key", "default");
162
+ expect(value).toBe("default");
163
+ });
164
+
165
+ test("returns value for existing top-level key", () => {
166
+ saveConfig({ ...DEFAULT_CONFIG, version: "test-version" });
167
+ const value = getConfigValue("version", "fallback");
168
+ expect(value).toBe("test-version");
169
+ });
170
+
171
+ test("returns value for nested key", () => {
172
+ saveConfig({
173
+ ...DEFAULT_CONFIG,
174
+ trust: { policy: "require_attestation" },
175
+ });
176
+ const value = getConfigValue("trust.policy", "fallback");
177
+ expect(value).toBe("require_attestation");
178
+ });
179
+
180
+ test("returns value for deeply nested key", () => {
181
+ saveConfig({
182
+ ...DEFAULT_CONFIG,
183
+ cache: { maxSizeMb: 999 },
184
+ });
185
+ const value = getConfigValue("cache.maxSizeMb", 0);
186
+ expect(value).toBe(999);
187
+ });
188
+
189
+ test("returns default for partially missing path", () => {
190
+ saveConfig({ ...DEFAULT_CONFIG });
191
+ const value = getConfigValue("trust.nonexistent.deep", "default");
192
+ expect(value).toBe("default");
193
+ });
194
+ });
195
+
196
+ describe("setConfigValue", () => {
197
+ test("sets top-level value and persists", () => {
198
+ saveConfig({ ...DEFAULT_CONFIG });
199
+ setConfigValue("version", "new-version");
200
+
201
+ const loaded = loadConfig();
202
+ expect(loaded.version).toBe("new-version");
203
+ });
204
+
205
+ test("sets nested value and persists", () => {
206
+ saveConfig({ ...DEFAULT_CONFIG });
207
+ setConfigValue("trust.policy", "allow");
208
+
209
+ const loaded = loadConfig();
210
+ expect(loaded.trust?.policy).toBe("allow");
211
+ });
212
+
213
+ test("creates intermediate objects if needed", () => {
214
+ saveConfig({ ...DEFAULT_CONFIG });
215
+ setConfigValue("cache.newField", "newValue");
216
+
217
+ const value = getConfigValue("cache.newField", "fallback");
218
+ expect(value).toBe("newValue");
219
+ });
220
+
221
+ test("preserves other values when setting", () => {
222
+ saveConfig({
223
+ ...DEFAULT_CONFIG,
224
+ trust: { auditors: ["github:alice"], policy: "prompt" },
225
+ });
226
+
227
+ setConfigValue("trust.policy", "require_attestation");
228
+
229
+ const loaded = loadConfig();
230
+ expect(loaded.trust?.policy).toBe("require_attestation");
231
+ expect(loaded.trust?.auditors).toContain("github:alice");
232
+ });
233
+
234
+ test("throws error for empty key", () => {
235
+ expect(() => setConfigValue("", "value")).toThrow("Invalid configuration key");
236
+ });
237
+ });
238
+
239
+ describe("resetConfig", () => {
240
+ test("resets config to defaults", () => {
241
+ // First, set a custom config
242
+ saveConfig({
243
+ version: "custom",
244
+ trust: { policy: "allow" },
245
+ cache: { maxSizeMb: 1 },
246
+ });
247
+
248
+ // Reset
249
+ resetConfig();
250
+
251
+ // Verify defaults restored
252
+ const loaded = loadConfig();
253
+ expect(loaded.version).toBe(DEFAULT_CONFIG.version);
254
+ expect(loaded.trust?.policy).toBe(DEFAULT_CONFIG.trust?.policy);
255
+ expect(loaded.cache?.maxSizeMb).toBe(DEFAULT_CONFIG.cache?.maxSizeMb);
256
+ });
257
+ });
258
+
259
+ describe("configExists", () => {
260
+ test("returns true after saving config", () => {
261
+ saveConfig({ ...DEFAULT_CONFIG });
262
+ expect(configExists()).toBe(true);
263
+ });
264
+ });
265
+
266
+ describe("YAML format", () => {
267
+ test("config file is valid YAML", () => {
268
+ saveConfig({
269
+ ...DEFAULT_CONFIG,
270
+ trust: { auditors: ["github:test"] },
271
+ });
272
+
273
+ // Read raw file and parse with yaml
274
+ const configPath = join(homedir(), ".enact", "config.yaml");
275
+ const content = readFileSync(configPath, "utf-8");
276
+
277
+ // Should not throw
278
+ const parsed = yaml.load(content);
279
+ expect(parsed).toBeDefined();
280
+ expect(typeof parsed).toBe("object");
281
+ });
282
+
283
+ test("config file has readable formatting", () => {
284
+ saveConfig({
285
+ ...DEFAULT_CONFIG,
286
+ trust: {
287
+ auditors: ["github:alice", "github:bob"],
288
+ policy: "require_attestation",
289
+ },
290
+ });
291
+
292
+ const configPath = join(homedir(), ".enact", "config.yaml");
293
+ const content = readFileSync(configPath, "utf-8");
294
+
295
+ // Should have proper indentation (not be on one line)
296
+ expect(content).toContain("\n");
297
+ expect(content).toContain("trust:");
298
+ expect(content).toContain("auditors:");
299
+ });
300
+ });
301
+
302
+ describe("error handling", () => {
303
+ test("loadConfig handles malformed YAML gracefully", () => {
304
+ // Save valid config first
305
+ saveConfig({ ...DEFAULT_CONFIG });
306
+
307
+ // Corrupt the file
308
+ const configPath = join(homedir(), ".enact", "config.yaml");
309
+ writeFileSync(configPath, "invalid: yaml: [unclosed", "utf-8");
310
+
311
+ // Should return defaults, not throw
312
+ const config = loadConfig();
313
+ expect(config).toBeDefined();
314
+ expect(config.version).toBe(DEFAULT_CONFIG.version);
315
+ });
316
+
317
+ test("loadConfig handles empty file gracefully", () => {
318
+ saveConfig({ ...DEFAULT_CONFIG });
319
+
320
+ const configPath = join(homedir(), ".enact", "config.yaml");
321
+ writeFileSync(configPath, "", "utf-8");
322
+
323
+ const config = loadConfig();
324
+ expect(config).toBeDefined();
325
+ expect(config.version).toBe(DEFAULT_CONFIG.version);
326
+ });
327
+
328
+ test("loadConfig handles null content gracefully", () => {
329
+ saveConfig({ ...DEFAULT_CONFIG });
330
+
331
+ const configPath = join(homedir(), ".enact", "config.yaml");
332
+ writeFileSync(configPath, "null", "utf-8");
333
+
334
+ const config = loadConfig();
335
+ expect(config).toBeDefined();
336
+ });
337
+ });
338
+
339
+ describe("merge behavior", () => {
340
+ test("merges partial config with defaults", () => {
341
+ // Save config with only some fields
342
+ const configPath = join(homedir(), ".enact", "config.yaml");
343
+ mkdirSync(join(homedir(), ".enact"), { recursive: true });
344
+ writeFileSync(configPath, yaml.dump({ trust: { policy: "require_attestation" } }), "utf-8");
345
+
346
+ const loaded = loadConfig();
347
+
348
+ // Custom value should be preserved
349
+ expect(loaded.trust?.policy).toBe("require_attestation");
350
+
351
+ // Default values should be filled in
352
+ expect(loaded.cache?.maxSizeMb).toBe(DEFAULT_CONFIG.cache?.maxSizeMb);
353
+ expect(loaded.execution?.defaultTimeout).toBe(DEFAULT_CONFIG.execution?.defaultTimeout);
354
+ });
355
+ });
356
+
357
+ describe("Trust Management", () => {
358
+ describe("Unified Identity Model", () => {
359
+ test("getTrustedIdentities returns empty array by default", () => {
360
+ resetConfig();
361
+ const identities = getTrustedIdentities();
362
+ expect(identities).toEqual([]);
363
+ });
364
+
365
+ test("addTrustedIdentity adds identity and returns true", () => {
366
+ resetConfig();
367
+ const added = addTrustedIdentity("github:alice");
368
+ expect(added).toBe(true);
369
+
370
+ const identities = getTrustedIdentities();
371
+ expect(identities).toContain("github:alice");
372
+ });
373
+
374
+ test("addTrustedIdentity returns false for duplicate", () => {
375
+ resetConfig();
376
+ addTrustedIdentity("github:alice");
377
+ const added = addTrustedIdentity("github:alice");
378
+ expect(added).toBe(false);
379
+ });
380
+
381
+ test("removeTrustedIdentity removes identity and returns true", () => {
382
+ resetConfig();
383
+ addTrustedIdentity("github:alice");
384
+ const removed = removeTrustedIdentity("github:alice");
385
+ expect(removed).toBe(true);
386
+
387
+ const identities = getTrustedIdentities();
388
+ expect(identities).not.toContain("github:alice");
389
+ });
390
+
391
+ test("removeTrustedIdentity returns false for non-existent identity", () => {
392
+ resetConfig();
393
+ const removed = removeTrustedIdentity("github:nonexistent");
394
+ expect(removed).toBe(false);
395
+ });
396
+
397
+ test("isIdentityTrusted checks exact match", () => {
398
+ resetConfig();
399
+ addTrustedIdentity("github:alice");
400
+ expect(isIdentityTrusted("github:alice")).toBe(true);
401
+ expect(isIdentityTrusted("github:bob")).toBe(false);
402
+ });
403
+
404
+ test("isIdentityTrusted supports wildcard patterns", () => {
405
+ resetConfig();
406
+ addTrustedIdentity("github:my-org/*");
407
+ expect(isIdentityTrusted("github:my-org/alice")).toBe(true);
408
+ expect(isIdentityTrusted("github:my-org/bob")).toBe(true);
409
+ expect(isIdentityTrusted("github:other-org/alice")).toBe(false);
410
+ });
411
+
412
+ test("isIdentityTrusted supports email wildcards", () => {
413
+ resetConfig();
414
+ addTrustedIdentity("*@company.com");
415
+ expect(isIdentityTrusted("alice@company.com")).toBe(true);
416
+ expect(isIdentityTrusted("bob@company.com")).toBe(true);
417
+ expect(isIdentityTrusted("alice@other.com")).toBe(false);
418
+ });
419
+ });
420
+
421
+ describe("Legacy Aliases (Backwards Compatibility)", () => {
422
+ test("getTrustedAuditors works as alias", () => {
423
+ resetConfig();
424
+ const auditors = getTrustedAuditors();
425
+ expect(auditors).toEqual([]);
426
+ });
427
+
428
+ test("addTrustedAuditor works as alias", () => {
429
+ resetConfig();
430
+ const added = addTrustedAuditor("github:alice");
431
+ expect(added).toBe(true);
432
+
433
+ const auditors = getTrustedAuditors();
434
+ expect(auditors).toContain("github:alice");
435
+ });
436
+
437
+ test("removeTrustedAuditor works as alias", () => {
438
+ resetConfig();
439
+ addTrustedAuditor("github:alice");
440
+ const removed = removeTrustedAuditor("github:alice");
441
+ expect(removed).toBe(true);
442
+ });
443
+
444
+ test("isAuditorTrusted works as alias", () => {
445
+ resetConfig();
446
+ addTrustedAuditor("github:alice");
447
+ expect(isAuditorTrusted("github:alice")).toBe(true);
448
+ });
449
+ });
450
+
451
+ describe("Trust Policy", () => {
452
+ test("getTrustPolicy returns default policy", () => {
453
+ resetConfig();
454
+ const policy = getTrustPolicy();
455
+ expect(policy).toBe("prompt");
456
+ });
457
+
458
+ test("getTrustPolicy returns configured policy", () => {
459
+ saveConfig({
460
+ ...DEFAULT_CONFIG,
461
+ trust: { policy: "require_attestation" },
462
+ });
463
+ const policy = getTrustPolicy();
464
+ expect(policy).toBe("require_attestation");
465
+ });
466
+
467
+ test("getMinimumAttestations returns default", () => {
468
+ resetConfig();
469
+ const min = getMinimumAttestations();
470
+ expect(min).toBe(1);
471
+ });
472
+
473
+ test("getMinimumAttestations returns configured value", () => {
474
+ saveConfig({
475
+ ...DEFAULT_CONFIG,
476
+ trust: { minimum_attestations: 3 },
477
+ });
478
+ const min = getMinimumAttestations();
479
+ expect(min).toBe(3);
480
+ });
481
+ });
482
+
483
+ describe("emailToProviderIdentity", () => {
484
+ test("converts GitHub noreply email", () => {
485
+ const result = emailToProviderIdentity("alice@users.noreply.github.com");
486
+ expect(result).toBe("github:alice");
487
+ });
488
+
489
+ test("converts GitHub email", () => {
490
+ const result = emailToProviderIdentity("alice@github.com");
491
+ expect(result).toBe("github:alice");
492
+ });
493
+
494
+ test("converts Google email", () => {
495
+ const result = emailToProviderIdentity("alice@gmail.com");
496
+ expect(result).toBe("google:alice");
497
+ });
498
+
499
+ test("converts Microsoft email", () => {
500
+ const result = emailToProviderIdentity("alice@outlook.com");
501
+ expect(result).toBe("microsoft:alice");
502
+ });
503
+
504
+ test("converts GitHub workflow URL", () => {
505
+ const result = emailToProviderIdentity("https://github.com/my-org/my-workflow");
506
+ expect(result).toBe("github:my-org/my-workflow");
507
+ });
508
+
509
+ test("returns email as-is for unknown providers", () => {
510
+ const result = emailToProviderIdentity("alice@unknown.com");
511
+ expect(result).toBe("alice@unknown.com");
512
+ });
513
+ });
514
+ });
515
+ });