@kya-os/mcp-i-core 1.3.7-canary.0 → 1.3.7-canary.clientinfo.20251126041014

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 (236) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-test$colon$coverage.log +4239 -0
  3. package/.turbo/turbo-test.log +2973 -0
  4. package/COMPLIANCE_IMPROVEMENT_REPORT.md +483 -0
  5. package/Composer 3.md +615 -0
  6. package/GPT-5.md +1169 -0
  7. package/OPUS-plan.md +352 -0
  8. package/PHASE_3_AND_4.1_SUMMARY.md +585 -0
  9. package/PHASE_3_SUMMARY.md +317 -0
  10. package/PHASE_4.1.3_SUMMARY.md +428 -0
  11. package/PHASE_4.1_COMPLETE.md +525 -0
  12. package/PHASE_4_USER_DID_IDENTITY_LINKING_PLAN.md +1240 -0
  13. package/SCHEMA_COMPLIANCE_REPORT.md +275 -0
  14. package/TEST_PLAN.md +571 -0
  15. package/coverage/coverage-final.json +57 -0
  16. package/dist/__tests__/utils/mock-providers.d.ts +1 -2
  17. package/dist/__tests__/utils/mock-providers.d.ts.map +1 -1
  18. package/dist/__tests__/utils/mock-providers.js.map +1 -1
  19. package/dist/cache/oauth-config-cache.d.ts +69 -0
  20. package/dist/cache/oauth-config-cache.d.ts.map +1 -0
  21. package/dist/cache/oauth-config-cache.js +76 -0
  22. package/dist/cache/oauth-config-cache.js.map +1 -0
  23. package/dist/identity/idp-token-resolver.d.ts +53 -0
  24. package/dist/identity/idp-token-resolver.d.ts.map +1 -0
  25. package/dist/identity/idp-token-resolver.js +108 -0
  26. package/dist/identity/idp-token-resolver.js.map +1 -0
  27. package/dist/identity/idp-token-storage.interface.d.ts +42 -0
  28. package/dist/identity/idp-token-storage.interface.d.ts.map +1 -0
  29. package/dist/identity/idp-token-storage.interface.js +12 -0
  30. package/dist/identity/idp-token-storage.interface.js.map +1 -0
  31. package/dist/identity/user-did-manager.d.ts +39 -1
  32. package/dist/identity/user-did-manager.d.ts.map +1 -1
  33. package/dist/identity/user-did-manager.js +69 -3
  34. package/dist/identity/user-did-manager.js.map +1 -1
  35. package/dist/index.d.ts +24 -0
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +43 -1
  38. package/dist/index.js.map +1 -1
  39. package/dist/runtime/audit-logger.d.ts +37 -0
  40. package/dist/runtime/audit-logger.d.ts.map +1 -0
  41. package/dist/runtime/audit-logger.js +9 -0
  42. package/dist/runtime/audit-logger.js.map +1 -0
  43. package/dist/runtime/base.d.ts +19 -2
  44. package/dist/runtime/base.d.ts.map +1 -1
  45. package/dist/runtime/base.js +227 -11
  46. package/dist/runtime/base.js.map +1 -1
  47. package/dist/services/access-control.service.d.ts.map +1 -1
  48. package/dist/services/access-control.service.js +199 -15
  49. package/dist/services/access-control.service.js.map +1 -1
  50. package/dist/services/authorization/authorization-registry.d.ts +29 -0
  51. package/dist/services/authorization/authorization-registry.d.ts.map +1 -0
  52. package/dist/services/authorization/authorization-registry.js +57 -0
  53. package/dist/services/authorization/authorization-registry.js.map +1 -0
  54. package/dist/services/authorization/types.d.ts +53 -0
  55. package/dist/services/authorization/types.d.ts.map +1 -0
  56. package/dist/services/authorization/types.js +10 -0
  57. package/dist/services/authorization/types.js.map +1 -0
  58. package/dist/services/batch-delegation.service.d.ts +53 -0
  59. package/dist/services/batch-delegation.service.d.ts.map +1 -0
  60. package/dist/services/batch-delegation.service.js +95 -0
  61. package/dist/services/batch-delegation.service.js.map +1 -0
  62. package/dist/services/index.d.ts +2 -0
  63. package/dist/services/index.d.ts.map +1 -1
  64. package/dist/services/index.js +4 -1
  65. package/dist/services/index.js.map +1 -1
  66. package/dist/services/oauth-config.service.d.ts +53 -0
  67. package/dist/services/oauth-config.service.d.ts.map +1 -0
  68. package/dist/services/oauth-config.service.js +141 -0
  69. package/dist/services/oauth-config.service.js.map +1 -0
  70. package/dist/services/oauth-provider-registry.d.ts +88 -0
  71. package/dist/services/oauth-provider-registry.d.ts.map +1 -0
  72. package/dist/services/oauth-provider-registry.js +128 -0
  73. package/dist/services/oauth-provider-registry.js.map +1 -0
  74. package/dist/services/oauth-service.d.ts +77 -0
  75. package/dist/services/oauth-service.d.ts.map +1 -0
  76. package/dist/services/oauth-service.js +373 -0
  77. package/dist/services/oauth-service.js.map +1 -0
  78. package/dist/services/oauth-token-retrieval.service.d.ts +49 -0
  79. package/dist/services/oauth-token-retrieval.service.d.ts.map +1 -0
  80. package/dist/services/oauth-token-retrieval.service.js +150 -0
  81. package/dist/services/oauth-token-retrieval.service.js.map +1 -0
  82. package/dist/services/provider-resolver.d.ts +48 -0
  83. package/dist/services/provider-resolver.d.ts.map +1 -0
  84. package/dist/services/provider-resolver.js +121 -0
  85. package/dist/services/provider-resolver.js.map +1 -0
  86. package/dist/services/provider-validator.d.ts +55 -0
  87. package/dist/services/provider-validator.d.ts.map +1 -0
  88. package/dist/services/provider-validator.js +135 -0
  89. package/dist/services/provider-validator.js.map +1 -0
  90. package/dist/services/session-registration.service.d.ts +80 -0
  91. package/dist/services/session-registration.service.d.ts.map +1 -0
  92. package/dist/services/session-registration.service.js +228 -0
  93. package/dist/services/session-registration.service.js.map +1 -0
  94. package/dist/services/tool-context-builder.d.ts +57 -0
  95. package/dist/services/tool-context-builder.d.ts.map +1 -0
  96. package/dist/services/tool-context-builder.js +125 -0
  97. package/dist/services/tool-context-builder.js.map +1 -0
  98. package/dist/services/tool-protection.service.d.ts +27 -0
  99. package/dist/services/tool-protection.service.d.ts.map +1 -1
  100. package/dist/services/tool-protection.service.js +194 -4
  101. package/dist/services/tool-protection.service.js.map +1 -1
  102. package/dist/types/oauth-required-error.d.ts +40 -0
  103. package/dist/types/oauth-required-error.d.ts.map +1 -0
  104. package/dist/types/oauth-required-error.js +40 -0
  105. package/dist/types/oauth-required-error.js.map +1 -0
  106. package/dist/utils/did-helpers.d.ts +33 -0
  107. package/dist/utils/did-helpers.d.ts.map +1 -1
  108. package/dist/utils/did-helpers.js +40 -0
  109. package/dist/utils/did-helpers.js.map +1 -1
  110. package/dist/utils/index.d.ts +1 -0
  111. package/dist/utils/index.d.ts.map +1 -1
  112. package/dist/utils/index.js +1 -0
  113. package/dist/utils/index.js.map +1 -1
  114. package/docs/API_REFERENCE.md +1362 -0
  115. package/docs/COMPLIANCE_MATRIX.md +691 -0
  116. package/docs/STATUSLIST2021_GUIDE.md +696 -0
  117. package/docs/W3C_VC_DELEGATION_GUIDE.md +710 -0
  118. package/package.json +23 -54
  119. package/scripts/audit-compliance.ts +724 -0
  120. package/src/__tests__/cache/tool-protection-cache.test.ts +640 -0
  121. package/src/__tests__/config/provider-runtime-config.test.ts +309 -0
  122. package/src/__tests__/delegation-e2e.test.ts +690 -0
  123. package/src/__tests__/identity/user-did-manager.test.ts +213 -0
  124. package/src/__tests__/index.test.ts +56 -0
  125. package/src/__tests__/integration/full-flow.test.ts +776 -0
  126. package/src/__tests__/integration.test.ts +281 -0
  127. package/src/__tests__/providers/base.test.ts +173 -0
  128. package/src/__tests__/providers/memory.test.ts +319 -0
  129. package/src/__tests__/regression/phase2-regression.test.ts +429 -0
  130. package/src/__tests__/runtime/audit-logger.test.ts +154 -0
  131. package/src/__tests__/runtime/base-extensions.test.ts +593 -0
  132. package/src/__tests__/runtime/base.test.ts +869 -0
  133. package/src/__tests__/runtime/delegation-flow.test.ts +164 -0
  134. package/src/__tests__/runtime/proof-client-did.test.ts +375 -0
  135. package/src/__tests__/runtime/route-interception.test.ts +686 -0
  136. package/src/__tests__/runtime/tool-protection-enforcement.test.ts +908 -0
  137. package/src/__tests__/services/agentshield-integration.test.ts +784 -0
  138. package/src/__tests__/services/cache-busting.test.ts +125 -0
  139. package/src/__tests__/services/oauth-service-pkce.test.ts +556 -0
  140. package/src/__tests__/services/provider-resolver-edge-cases.test.ts +591 -0
  141. package/src/__tests__/services/tool-protection-oauth-provider.test.ts +480 -0
  142. package/src/__tests__/services/tool-protection.service.test.ts +1366 -0
  143. package/src/__tests__/utils/mock-providers.ts +340 -0
  144. package/src/cache/oauth-config-cache.d.ts +69 -0
  145. package/src/cache/oauth-config-cache.d.ts.map +1 -0
  146. package/src/cache/oauth-config-cache.js.map +1 -0
  147. package/src/cache/oauth-config-cache.ts +123 -0
  148. package/src/cache/tool-protection-cache.ts +171 -0
  149. package/src/compliance/EXAMPLE.md +412 -0
  150. package/src/compliance/__tests__/schema-verifier.test.ts +797 -0
  151. package/src/compliance/index.ts +8 -0
  152. package/src/compliance/schema-registry.ts +460 -0
  153. package/src/compliance/schema-verifier.ts +708 -0
  154. package/src/config/__tests__/remote-config.spec.ts +268 -0
  155. package/src/config/remote-config.ts +174 -0
  156. package/src/config.ts +309 -0
  157. package/src/delegation/__tests__/audience-validator.test.ts +112 -0
  158. package/src/delegation/__tests__/bitstring.test.ts +346 -0
  159. package/src/delegation/__tests__/cascading-revocation.test.ts +628 -0
  160. package/src/delegation/__tests__/delegation-graph.test.ts +584 -0
  161. package/src/delegation/__tests__/utils.test.ts +152 -0
  162. package/src/delegation/__tests__/vc-issuer.test.ts +442 -0
  163. package/src/delegation/__tests__/vc-verifier.test.ts +922 -0
  164. package/src/delegation/audience-validator.ts +52 -0
  165. package/src/delegation/bitstring.ts +278 -0
  166. package/src/delegation/cascading-revocation.ts +370 -0
  167. package/src/delegation/delegation-graph.ts +299 -0
  168. package/src/delegation/index.ts +14 -0
  169. package/src/delegation/statuslist-manager.ts +353 -0
  170. package/src/delegation/storage/__tests__/memory-graph-storage.test.ts +366 -0
  171. package/src/delegation/storage/__tests__/memory-statuslist-storage.test.ts +228 -0
  172. package/src/delegation/storage/index.ts +9 -0
  173. package/src/delegation/storage/memory-graph-storage.ts +178 -0
  174. package/src/delegation/storage/memory-statuslist-storage.ts +77 -0
  175. package/src/delegation/utils.ts +42 -0
  176. package/src/delegation/vc-issuer.ts +232 -0
  177. package/src/delegation/vc-verifier.ts +568 -0
  178. package/src/identity/idp-token-resolver.ts +147 -0
  179. package/src/identity/idp-token-storage.interface.ts +59 -0
  180. package/src/identity/user-did-manager.ts +370 -0
  181. package/src/index.ts +271 -0
  182. package/src/providers/base.d.ts +91 -0
  183. package/src/providers/base.d.ts.map +1 -0
  184. package/src/providers/base.js.map +1 -0
  185. package/src/providers/base.ts +96 -0
  186. package/src/providers/memory.ts +142 -0
  187. package/src/runtime/audit-logger.ts +39 -0
  188. package/src/runtime/base.ts +1329 -0
  189. package/src/services/__tests__/access-control.integration.test.ts +443 -0
  190. package/src/services/__tests__/access-control.proof-response-validation.test.ts +578 -0
  191. package/src/services/__tests__/access-control.service.test.ts +970 -0
  192. package/src/services/__tests__/batch-delegation.service.test.ts +351 -0
  193. package/src/services/__tests__/crypto.service.test.ts +531 -0
  194. package/src/services/__tests__/oauth-provider-registry.test.ts +142 -0
  195. package/src/services/__tests__/proof-verifier.integration.test.ts +485 -0
  196. package/src/services/__tests__/proof-verifier.test.ts +489 -0
  197. package/src/services/__tests__/provider-resolution.integration.test.ts +202 -0
  198. package/src/services/__tests__/provider-resolver.test.ts +213 -0
  199. package/src/services/__tests__/storage.service.test.ts +358 -0
  200. package/src/services/access-control.service.ts +990 -0
  201. package/src/services/authorization/authorization-registry.ts +66 -0
  202. package/src/services/authorization/types.ts +71 -0
  203. package/src/services/batch-delegation.service.ts +137 -0
  204. package/src/services/crypto.service.ts +302 -0
  205. package/src/services/errors.ts +76 -0
  206. package/src/services/index.ts +18 -0
  207. package/src/services/oauth-config.service.d.ts +53 -0
  208. package/src/services/oauth-config.service.d.ts.map +1 -0
  209. package/src/services/oauth-config.service.js.map +1 -0
  210. package/src/services/oauth-config.service.ts +192 -0
  211. package/src/services/oauth-provider-registry.d.ts +57 -0
  212. package/src/services/oauth-provider-registry.d.ts.map +1 -0
  213. package/src/services/oauth-provider-registry.js.map +1 -0
  214. package/src/services/oauth-provider-registry.ts +141 -0
  215. package/src/services/oauth-service.ts +544 -0
  216. package/src/services/oauth-token-retrieval.service.ts +245 -0
  217. package/src/services/proof-verifier.ts +478 -0
  218. package/src/services/provider-resolver.d.ts +48 -0
  219. package/src/services/provider-resolver.d.ts.map +1 -0
  220. package/src/services/provider-resolver.js.map +1 -0
  221. package/src/services/provider-resolver.ts +146 -0
  222. package/src/services/provider-validator.ts +170 -0
  223. package/src/services/session-registration.service.ts +317 -0
  224. package/src/services/storage.service.ts +566 -0
  225. package/src/services/tool-context-builder.ts +172 -0
  226. package/src/services/tool-protection.service.ts +982 -0
  227. package/src/types/oauth-required-error.ts +63 -0
  228. package/src/types/tool-protection.ts +155 -0
  229. package/src/utils/__tests__/did-helpers.test.ts +101 -0
  230. package/src/utils/base64.ts +148 -0
  231. package/src/utils/cors.ts +83 -0
  232. package/src/utils/did-helpers.ts +150 -0
  233. package/src/utils/index.ts +8 -0
  234. package/src/utils/storage-keys.ts +278 -0
  235. package/tsconfig.json +21 -0
  236. package/vitest.config.ts +56 -0
@@ -0,0 +1,776 @@
1
+ /**
2
+ * Full Flow Integration Tests
3
+ *
4
+ * End-to-end integration tests covering:
5
+ * - Complete handshake → tool call → proof flow
6
+ * - Tool protection enforcement
7
+ * - Delegation flow (intercept → authorize → resume)
8
+ * - AgentShield integration
9
+ * - Caching behavior
10
+ */
11
+
12
+ import { describe, test, expect, beforeEach, vi, afterEach } from "vitest";
13
+ import { MCPIRuntimeBase } from "../../runtime/base";
14
+ import { ToolProtectionService } from "../../services/tool-protection.service";
15
+ import {
16
+ InMemoryToolProtectionCache,
17
+ type ToolProtectionCache,
18
+ } from "../../cache/tool-protection-cache";
19
+ import {
20
+ MemoryStorageProvider,
21
+ MemoryNonceCacheProvider,
22
+ MemoryIdentityProvider,
23
+ } from "../../providers/memory";
24
+ import {
25
+ createMockProviders,
26
+ MockCryptoProvider,
27
+ MockClockProvider,
28
+ MockFetchProvider,
29
+ } from "../utils/mock-providers";
30
+ import type { ToolProtectionServiceConfig } from "../../types/tool-protection";
31
+ import type { ProviderRuntimeConfig } from "../../config";
32
+
33
+ // Type declaration for global (polyfilled by Vitest)
34
+ declare const global: typeof globalThis;
35
+
36
+ // Mock global fetch
37
+ global.fetch = vi.fn();
38
+
39
+ describe("Full Flow Integration", () => {
40
+ let runtime: MCPIRuntimeBase;
41
+ let toolProtectionService: ToolProtectionService;
42
+ let cache: ToolProtectionCache;
43
+ let config: ProviderRuntimeConfig;
44
+ let mockProviders: ReturnType<typeof createMockProviders>;
45
+ const mockAgentDid =
46
+ "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK";
47
+
48
+ beforeEach(async () => {
49
+ vi.clearAllMocks();
50
+ mockProviders = createMockProviders();
51
+
52
+ cache = new InMemoryToolProtectionCache();
53
+ const toolProtectionConfig: ToolProtectionServiceConfig = {
54
+ apiUrl: "https://kya.vouched.id",
55
+ apiKey: "test-api-key",
56
+ projectId: "test-project",
57
+ cacheTtl: 300000,
58
+ debug: false,
59
+ };
60
+ toolProtectionService = new ToolProtectionService(
61
+ toolProtectionConfig,
62
+ cache
63
+ );
64
+
65
+ config = {
66
+ ...mockProviders,
67
+ environment: "development",
68
+ session: {
69
+ timestampSkewSeconds: 120,
70
+ ttlMinutes: 30,
71
+ },
72
+ audit: {
73
+ enabled: true,
74
+ logFunction: vi.fn(),
75
+ includePayloads: true,
76
+ },
77
+ toolProtection: {
78
+ source: "agentshield",
79
+ agentShield: {
80
+ apiUrl: "https://kya.vouched.id",
81
+ apiKey: "test-api-key",
82
+ },
83
+ },
84
+ };
85
+
86
+ runtime = new MCPIRuntimeBase(config);
87
+ await runtime.initialize();
88
+ });
89
+
90
+ afterEach(() => {
91
+ vi.restoreAllMocks();
92
+ });
93
+
94
+ describe("Complete handshake → tool call → proof flow", () => {
95
+ test("should complete full authentication and tool execution cycle", async () => {
96
+ // Step 1: Client initiates handshake
97
+ const handshakeRequest = {
98
+ clientDid: "did:key:zclient789",
99
+ audience: "https://app.example.com",
100
+ };
101
+
102
+ const handshakeResponse = await runtime.handleHandshake(handshakeRequest);
103
+
104
+ expect(handshakeResponse.sessionId).toBeDefined();
105
+ expect(handshakeResponse.agentDid).toMatch(/^did:key:z/);
106
+ expect(handshakeResponse.signature).toBeDefined();
107
+ expect(handshakeResponse.capabilities).toContain("identity");
108
+
109
+ // Step 2: Get session and issue nonce
110
+ const session = await runtime.getCurrentSession();
111
+ expect(session).toBeDefined();
112
+ expect(session?.clientDid).toBe(handshakeRequest.clientDid);
113
+ expect(session?.audience).toBe(handshakeRequest.audience);
114
+
115
+ const nonce = await runtime.issueNonce(session!.id);
116
+ session!.nonce = nonce;
117
+
118
+ // Step 3: Execute unprotected tool
119
+ const toolHandler = async (args: any) => {
120
+ return {
121
+ message: `Hello ${args.name}!`,
122
+ timestamp: Date.now(),
123
+ };
124
+ };
125
+
126
+ const result = await runtime.processToolCall(
127
+ "greetingTool",
128
+ { name: "Alice" },
129
+ toolHandler,
130
+ session
131
+ );
132
+
133
+ expect(result.message).toBe("Hello Alice!");
134
+
135
+ // Step 4: Verify proof was created
136
+ const proof = runtime.getLastProof();
137
+ expect(proof).toBeDefined();
138
+ expect(proof?.nonce).toBe(nonce);
139
+ expect(proof?.sessionId).toBe(session!.id);
140
+ expect(proof?.audience).toBe("https://app.example.com");
141
+ });
142
+
143
+ test("should handle multiple tool calls in sequence", async () => {
144
+ const handshakeResponse = await runtime.handleHandshake({
145
+ clientDid: "did:key:zclient789",
146
+ audience: "https://app.example.com",
147
+ });
148
+
149
+ const session = await runtime.getCurrentSession();
150
+ expect(session).toBeDefined();
151
+
152
+ const nonce1 = await runtime.issueNonce(session!.id);
153
+ session!.nonce = nonce1;
154
+
155
+ // First tool call
156
+ const result1 = await runtime.processToolCall(
157
+ "tool1",
158
+ { arg: "value1" },
159
+ async (args) => ({ result: args.arg }),
160
+ session
161
+ );
162
+
163
+ expect(result1.result).toBe("value1");
164
+
165
+ // Second tool call with new nonce
166
+ const nonce2 = await runtime.issueNonce(session!.id);
167
+ session!.nonce = nonce2;
168
+
169
+ const result2 = await runtime.processToolCall(
170
+ "tool2",
171
+ { arg: "value2" },
172
+ async (args) => ({ result: args.arg }),
173
+ session
174
+ );
175
+
176
+ expect(result2.result).toBe("value2");
177
+
178
+ // Verify both proofs were created
179
+ const proof = runtime.getLastProof();
180
+ expect(proof?.nonce).toBe(nonce2);
181
+ });
182
+
183
+ test("should propagate client DID through entire flow", async () => {
184
+ const clientDid = "did:key:zclient789";
185
+ const handshakeResponse = await runtime.handleHandshake({
186
+ clientDid,
187
+ audience: "https://app.example.com",
188
+ });
189
+
190
+ const session = await runtime.getCurrentSession();
191
+ expect(session?.clientDid).toBe(clientDid);
192
+
193
+ const nonce = await runtime.issueNonce(session!.id);
194
+ session!.nonce = nonce;
195
+
196
+ await runtime.processToolCall(
197
+ "testTool",
198
+ { test: "data" },
199
+ async () => ({}),
200
+ session
201
+ );
202
+
203
+ const proof = runtime.getLastProof();
204
+ expect(proof).toBeDefined();
205
+ // Client DID should be in proof metadata
206
+ expect((proof as any).clientDid || session?.clientDid).toBe(clientDid);
207
+ });
208
+ });
209
+
210
+ describe("Tool protection enforcement flow", () => {
211
+ test("should allow unprotected tool calls", async () => {
212
+ // Mock API response with unprotected tool
213
+ const apiResponse = {
214
+ success: true,
215
+ data: {
216
+ toolProtections: {
217
+ search: {
218
+ requiresDelegation: false,
219
+ requiredScopes: [],
220
+ },
221
+ },
222
+ },
223
+ metadata: {},
224
+ };
225
+
226
+ (global.fetch as any).mockResolvedValueOnce({
227
+ ok: true,
228
+ json: async () => apiResponse,
229
+ });
230
+
231
+ const handshakeResponse = await runtime.handleHandshake({
232
+ clientDid: "did:key:zclient",
233
+ audience: "https://app.example.com",
234
+ });
235
+
236
+ const session = await runtime.getCurrentSession();
237
+ const nonce = await runtime.issueNonce(session!.id);
238
+ session!.nonce = nonce;
239
+
240
+ // Mock tool protection service
241
+ const protection = await toolProtectionService.checkToolProtection(
242
+ "search",
243
+ mockAgentDid
244
+ );
245
+ expect(protection).toBeNull(); // No protection required
246
+
247
+ // Tool call should proceed normally
248
+ const result = await runtime.processToolCall(
249
+ "search",
250
+ { query: "test" },
251
+ async (args) => ({ results: [] }),
252
+ session
253
+ );
254
+
255
+ expect(result).toBeDefined();
256
+ });
257
+
258
+ test("should intercept protected tool calls without delegation", async () => {
259
+ // Mock API response with protected tool
260
+ const apiResponse = {
261
+ success: true,
262
+ data: {
263
+ toolProtections: {
264
+ checkout: {
265
+ requiresDelegation: true,
266
+ requiredScopes: ["cart:write"],
267
+ },
268
+ },
269
+ },
270
+ metadata: {},
271
+ };
272
+
273
+ (global.fetch as any).mockResolvedValueOnce({
274
+ ok: true,
275
+ json: async () => apiResponse,
276
+ });
277
+
278
+ const handshakeResponse = await runtime.handleHandshake({
279
+ clientDid: "did:key:zclient",
280
+ audience: "https://app.example.com",
281
+ });
282
+
283
+ const session = await runtime.getCurrentSession();
284
+ const nonce = await runtime.issueNonce(session!.id);
285
+ session!.nonce = nonce;
286
+
287
+ // Check protection
288
+ const protection = await toolProtectionService.checkToolProtection(
289
+ "checkout",
290
+ mockAgentDid
291
+ );
292
+ expect(protection).toBeDefined();
293
+ expect(protection?.requiresDelegation).toBe(true);
294
+
295
+ // Tool call should be intercepted
296
+ // Note: Actual interception happens in runtime.processToolCall
297
+ // This test verifies the protection check works correctly
298
+ expect(protection?.requiredScopes).toContain("cart:write");
299
+ });
300
+ });
301
+
302
+ describe("Delegation flow (intercept → authorize → resume)", () => {
303
+ test("should store intercepted call for resumption", async () => {
304
+ const handshakeResponse = await runtime.handleHandshake({
305
+ clientDid: "did:key:zclient",
306
+ audience: "https://app.example.com",
307
+ });
308
+
309
+ const session = await runtime.getCurrentSession();
310
+ expect(session).toBeDefined();
311
+
312
+ // Simulate intercepted call storage
313
+ const interceptedCall = {
314
+ toolName: "checkout",
315
+ args: { cartId: "123" },
316
+ sessionId: session!.id,
317
+ timestamp: Date.now(),
318
+ expiresAt: Date.now() + 3600000, // 1 hour
319
+ };
320
+
321
+ // Verify intercepted call structure
322
+ expect(interceptedCall.toolName).toBe("checkout");
323
+ expect(interceptedCall.args.cartId).toBe("123");
324
+ expect(interceptedCall.sessionId).toBe(session!.id);
325
+ });
326
+
327
+ test("should generate resume token for intercepted calls", async () => {
328
+ const handshakeResponse = await runtime.handleHandshake({
329
+ clientDid: "did:key:zclient",
330
+ audience: "https://app.example.com",
331
+ });
332
+
333
+ const session = await runtime.getCurrentSession();
334
+
335
+ // Generate resume token (simulated)
336
+ const resumeToken = `resume_${session!.id}_${Date.now()}`;
337
+
338
+ expect(resumeToken).toMatch(/^resume_/);
339
+ expect(resumeToken).toContain(session!.id);
340
+ });
341
+
342
+ test("should build consent URL with resume token", async () => {
343
+ const handshakeResponse = await runtime.handleHandshake({
344
+ clientDid: "did:key:zclient",
345
+ audience: "https://app.example.com",
346
+ });
347
+
348
+ const session = await runtime.getCurrentSession();
349
+ const resumeToken = `resume_${session!.id}_${Date.now()}`;
350
+
351
+ // Build consent URL
352
+ const consentUrl = `https://kya.vouched.id/consent?resume_token=${resumeToken}&tool=checkout&scopes=cart:write`;
353
+
354
+ expect(consentUrl).toContain("resume_token=");
355
+ expect(consentUrl).toContain("tool=checkout");
356
+ expect(consentUrl).toContain("scopes=cart:write");
357
+ });
358
+ });
359
+
360
+ describe("AgentShield integration flow", () => {
361
+ test("should fetch tool protection config from AgentShield", async () => {
362
+ const apiResponse = {
363
+ success: true,
364
+ data: {
365
+ toolProtections: {
366
+ protected_tool: {
367
+ requiresDelegation: true,
368
+ requiredScopes: ["scope1"],
369
+ },
370
+ },
371
+ },
372
+ metadata: {},
373
+ };
374
+
375
+ (global.fetch as any).mockResolvedValueOnce({
376
+ ok: true,
377
+ json: async () => apiResponse,
378
+ });
379
+
380
+ const config =
381
+ await toolProtectionService.getToolProtectionConfig(mockAgentDid);
382
+
383
+ expect(config.toolProtections.protected_tool).toBeDefined();
384
+ expect(config.toolProtections.protected_tool.requiresDelegation).toBe(
385
+ true
386
+ );
387
+ expect(global.fetch).toHaveBeenCalledWith(
388
+ expect.stringContaining(
389
+ "/api/v1/bouncer/projects/test-project/tool-protections"
390
+ ),
391
+ expect.any(Object)
392
+ );
393
+ });
394
+
395
+ test("should cache tool protection config", async () => {
396
+ const apiResponse = {
397
+ success: true,
398
+ data: {
399
+ toolProtections: {
400
+ tool1: {
401
+ requiresDelegation: true,
402
+ requiredScopes: ["scope1"],
403
+ },
404
+ },
405
+ },
406
+ metadata: {},
407
+ };
408
+
409
+ (global.fetch as any).mockResolvedValueOnce({
410
+ ok: true,
411
+ json: async () => apiResponse,
412
+ });
413
+
414
+ // First call - should fetch from API
415
+ const config1 =
416
+ await toolProtectionService.getToolProtectionConfig(mockAgentDid);
417
+
418
+ // Second call - should use cache
419
+ const config2 =
420
+ await toolProtectionService.getToolProtectionConfig(mockAgentDid);
421
+
422
+ expect(config1).toEqual(config2);
423
+ expect(global.fetch).toHaveBeenCalledTimes(1);
424
+ });
425
+
426
+ test("should use fallback config when API fails", async () => {
427
+ const fallbackConfig = {
428
+ toolProtections: {
429
+ fallback_tool: {
430
+ requiresDelegation: true,
431
+ requiredScopes: ["fallback"],
432
+ },
433
+ },
434
+ };
435
+
436
+ const toolProtectionConfig: ToolProtectionServiceConfig = {
437
+ apiUrl: "https://kya.vouched.id",
438
+ apiKey: "test-api-key",
439
+ projectId: "test-project",
440
+ cacheTtl: 300000,
441
+ fallbackConfig,
442
+ debug: false,
443
+ };
444
+
445
+ const serviceWithFallback = new ToolProtectionService(
446
+ toolProtectionConfig,
447
+ cache
448
+ );
449
+
450
+ (global.fetch as any).mockRejectedValueOnce(new Error("Network error"));
451
+
452
+ const result =
453
+ await serviceWithFallback.getToolProtectionConfig(mockAgentDid);
454
+
455
+ expect(result).toEqual(fallbackConfig);
456
+ });
457
+ });
458
+
459
+ describe("Session expiry handling", () => {
460
+ test("should handle expired sessions correctly", async () => {
461
+ const clock = mockProviders.clockProvider as MockClockProvider;
462
+ const currentTime = Date.now();
463
+ clock.setTime(currentTime);
464
+
465
+ await runtime.handleHandshake({
466
+ clientDid: "did:key:zclient",
467
+ audience: "https://app.example.com",
468
+ });
469
+
470
+ let session = await runtime.getCurrentSession();
471
+ expect(session).toBeDefined();
472
+
473
+ // Advance time past expiry (30 minutes + 1 second)
474
+ clock.setTime(currentTime + 30 * 60 * 1000 + 1000);
475
+
476
+ session = await runtime.getCurrentSession();
477
+ expect(session).toBeNull();
478
+ });
479
+
480
+ test("should reject tool calls with expired session", async () => {
481
+ const clock = mockProviders.clockProvider as MockClockProvider;
482
+ const currentTime = Date.now();
483
+ clock.setTime(currentTime);
484
+
485
+ await runtime.handleHandshake({
486
+ clientDid: "did:key:zclient",
487
+ audience: "https://app.example.com",
488
+ });
489
+
490
+ const session = await runtime.getCurrentSession();
491
+ expect(session).toBeDefined();
492
+
493
+ // Advance time past expiry
494
+ clock.setTime(currentTime + 30 * 60 * 1000 + 1000);
495
+
496
+ // Session should be expired
497
+ const expiredSession = await runtime.getCurrentSession();
498
+ expect(expiredSession).toBeNull();
499
+ });
500
+ });
501
+
502
+ describe("Error handling in full flow", () => {
503
+ test("should handle tool protection service errors gracefully", async () => {
504
+ // Mock API failure
505
+ (global.fetch as any).mockRejectedValueOnce(new Error("Network error"));
506
+
507
+ // Should fail-closed (deny-all) by default
508
+ const config =
509
+ await toolProtectionService.getToolProtectionConfig(mockAgentDid);
510
+
511
+ expect(config.toolProtections['*']).toBeDefined();
512
+ expect(config.toolProtections['*']?.requiresDelegation).toBe(true);
513
+ });
514
+
515
+ test("should handle invalid API responses", async () => {
516
+ (global.fetch as any).mockResolvedValueOnce({
517
+ ok: true,
518
+ json: async () => ({
519
+ success: false,
520
+ error: "Invalid request",
521
+ }),
522
+ });
523
+
524
+ await expect(
525
+ toolProtectionService.getToolProtectionConfig(mockAgentDid)
526
+ ).rejects.toThrow("API returned success: false");
527
+ });
528
+
529
+ test("should handle network timeouts", async () => {
530
+ const fallbackConfig = {
531
+ toolProtections: {
532
+ tool1: {
533
+ requiresDelegation: true,
534
+ requiredScopes: ["scope1"],
535
+ },
536
+ },
537
+ };
538
+
539
+ const toolProtectionConfig: ToolProtectionServiceConfig = {
540
+ apiUrl: "https://kya.vouched.id",
541
+ apiKey: "test-api-key",
542
+ projectId: "test-project",
543
+ cacheTtl: 300000,
544
+ fallbackConfig,
545
+ debug: false,
546
+ };
547
+
548
+ const serviceWithFallback = new ToolProtectionService(
549
+ toolProtectionConfig,
550
+ cache
551
+ );
552
+
553
+ (global.fetch as any).mockRejectedValueOnce(new Error("Network timeout"));
554
+
555
+ const result =
556
+ await serviceWithFallback.getToolProtectionConfig(mockAgentDid);
557
+
558
+ expect(result).toEqual(fallbackConfig);
559
+ });
560
+ });
561
+
562
+ describe("Cache integration in full flow", () => {
563
+ test("should share cache across multiple service instances", async () => {
564
+ const apiResponse = {
565
+ success: true,
566
+ data: {
567
+ toolProtections: {
568
+ tool1: {
569
+ requiresDelegation: true,
570
+ requiredScopes: ["scope1"],
571
+ },
572
+ },
573
+ },
574
+ metadata: {},
575
+ };
576
+
577
+ (global.fetch as any).mockResolvedValueOnce({
578
+ ok: true,
579
+ json: async () => apiResponse,
580
+ });
581
+
582
+ const service1 = new ToolProtectionService(
583
+ {
584
+ apiUrl: "https://kya.vouched.id",
585
+ apiKey: "test-api-key",
586
+ projectId: "test-project",
587
+ cacheTtl: 300000,
588
+ },
589
+ cache
590
+ );
591
+
592
+ const service2 = new ToolProtectionService(
593
+ {
594
+ apiUrl: "https://kya.vouched.id",
595
+ apiKey: "test-api-key",
596
+ projectId: "test-project",
597
+ cacheTtl: 300000,
598
+ },
599
+ cache
600
+ );
601
+
602
+ // First service fetches from API
603
+ await service1.getToolProtectionConfig(mockAgentDid);
604
+
605
+ // Second service should use cached value
606
+ const config2 = await service2.getToolProtectionConfig(mockAgentDid);
607
+
608
+ expect(global.fetch).toHaveBeenCalledTimes(1);
609
+ expect(config2.toolProtections.tool1).toBeDefined();
610
+ });
611
+
612
+ test("should clear cache when needed", async () => {
613
+ const apiResponse = {
614
+ success: true,
615
+ data: {
616
+ toolProtections: {
617
+ tool1: {
618
+ requiresDelegation: true,
619
+ requiredScopes: ["scope1"],
620
+ },
621
+ },
622
+ },
623
+ metadata: {},
624
+ };
625
+
626
+ (global.fetch as any).mockResolvedValue({
627
+ ok: true,
628
+ json: async () => apiResponse,
629
+ });
630
+
631
+ // Fetch and cache
632
+ await toolProtectionService.getToolProtectionConfig(mockAgentDid);
633
+
634
+ // Clear cache
635
+ await toolProtectionService.clearCache(mockAgentDid);
636
+
637
+ // Next fetch should go to API again
638
+ await toolProtectionService.getToolProtectionConfig(mockAgentDid);
639
+
640
+ expect(global.fetch).toHaveBeenCalledTimes(2);
641
+ });
642
+ });
643
+
644
+ describe("Real-world e-commerce scenario", () => {
645
+ test("should handle complete e-commerce flow with tool protection", async () => {
646
+ // Step 1: Set up tool protection config
647
+ const apiResponse = {
648
+ success: true,
649
+ data: {
650
+ toolProtections: {
651
+ search_products: {
652
+ requiresDelegation: false,
653
+ requiredScopes: [],
654
+ },
655
+ add_to_cart: {
656
+ requiresDelegation: true,
657
+ requiredScopes: ["cart:write"],
658
+ },
659
+ checkout: {
660
+ requiresDelegation: true,
661
+ requiredScopes: ["cart:write", "payment:process"],
662
+ },
663
+ },
664
+ },
665
+ metadata: {},
666
+ };
667
+
668
+ (global.fetch as any).mockResolvedValueOnce({
669
+ ok: true,
670
+ json: async () => apiResponse,
671
+ });
672
+
673
+ // Step 2: Handshake
674
+ const handshakeResponse = await runtime.handleHandshake({
675
+ clientDid: "did:key:zclient",
676
+ audience: "https://shop.example.com",
677
+ });
678
+
679
+ const session = await runtime.getCurrentSession();
680
+ expect(session).toBeDefined();
681
+
682
+ // Step 3: Unprotected tool call (search)
683
+ const nonce1 = await runtime.issueNonce(session!.id);
684
+ session!.nonce = nonce1;
685
+
686
+ const searchProtection = await toolProtectionService.checkToolProtection(
687
+ "search_products",
688
+ mockAgentDid
689
+ );
690
+ expect(searchProtection).toBeNull(); // No protection
691
+
692
+ const searchResult = await runtime.processToolCall(
693
+ "search_products",
694
+ { query: "laptop" },
695
+ async () => ({ products: [] }),
696
+ session
697
+ );
698
+
699
+ expect(searchResult).toBeDefined();
700
+
701
+ // Step 4: Check protection for add_to_cart (should require delegation)
702
+ const nonce2 = await runtime.issueNonce(session!.id);
703
+ session!.nonce = nonce2;
704
+
705
+ const cartProtection = await toolProtectionService.checkToolProtection(
706
+ "add_to_cart",
707
+ mockAgentDid
708
+ );
709
+
710
+ expect(cartProtection).toBeDefined();
711
+ expect(cartProtection?.requiresDelegation).toBe(true);
712
+ expect(cartProtection?.requiredScopes).toContain("cart:write");
713
+
714
+ // Step 5: Verify proof was created for search tool call (not for protection check)
715
+ const proof = runtime.getLastProof();
716
+ expect(proof).toBeDefined();
717
+ expect(proof?.nonce).toBe(nonce1); // Proof from search_products call
718
+ });
719
+ });
720
+
721
+ describe("Concurrent operations", () => {
722
+ test("should handle concurrent tool calls", async () => {
723
+ await runtime.handleHandshake({
724
+ clientDid: "did:key:zclient",
725
+ audience: "https://app.example.com",
726
+ });
727
+
728
+ const session = await runtime.getCurrentSession();
729
+ expect(session).toBeDefined();
730
+
731
+ // Issue multiple nonces
732
+ const nonces = await Promise.all([
733
+ runtime.issueNonce(session!.id),
734
+ runtime.issueNonce(session!.id),
735
+ runtime.issueNonce(session!.id),
736
+ ]);
737
+
738
+ expect(nonces).toHaveLength(3);
739
+ expect(new Set(nonces).size).toBe(3); // All unique
740
+ });
741
+
742
+ test("should handle concurrent cache operations", async () => {
743
+ const apiResponse = {
744
+ success: true,
745
+ data: {
746
+ toolProtections: {
747
+ tool1: {
748
+ requiresDelegation: true,
749
+ requiredScopes: ["scope1"],
750
+ },
751
+ },
752
+ },
753
+ metadata: {},
754
+ };
755
+
756
+ (global.fetch as any).mockResolvedValue({
757
+ ok: true,
758
+ json: async () => apiResponse,
759
+ });
760
+
761
+ // Concurrent config fetches
762
+ const promises = [
763
+ toolProtectionService.getToolProtectionConfig(mockAgentDid),
764
+ toolProtectionService.getToolProtectionConfig(mockAgentDid),
765
+ toolProtectionService.getToolProtectionConfig(mockAgentDid),
766
+ ];
767
+
768
+ const results = await Promise.all(promises);
769
+
770
+ // All should return same config
771
+ results.forEach((result) => {
772
+ expect(result.toolProtections.tool1).toBeDefined();
773
+ });
774
+ });
775
+ });
776
+ });