@vellumai/credential-executor 0.10.7-dev.202607102035.64f07ea → 0.10.7-staging.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 (65) hide show
  1. package/Dockerfile +1 -1
  2. package/node_modules/@vellumai/service-contracts/package.json +2 -1
  3. package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +2 -0
  4. package/node_modules/@vellumai/service-contracts/src/__tests__/grants.test.ts +686 -0
  5. package/node_modules/@vellumai/service-contracts/src/credential-rpc.ts +5 -3
  6. package/node_modules/@vellumai/service-contracts/src/grants.ts +184 -0
  7. package/node_modules/@vellumai/service-contracts/src/index.ts +4 -2
  8. package/node_modules/@vellumai/service-contracts/src/rendering.ts +135 -0
  9. package/node_modules/@vellumai/service-contracts/src/rpc.ts +447 -4
  10. package/package.json +3 -2
  11. package/src/__tests__/bulk-set-credentials.test.ts +1 -1
  12. package/src/__tests__/command-executor.test.ts +1879 -0
  13. package/src/__tests__/command-validator.test.ts +1405 -0
  14. package/src/__tests__/command-workspace.test.ts +1050 -0
  15. package/src/__tests__/grant-store.test.ts +689 -0
  16. package/src/__tests__/http-executor.test.ts +1336 -0
  17. package/src/__tests__/http-policy.test.ts +1069 -0
  18. package/src/__tests__/local-materializers.test.ts +860 -0
  19. package/src/__tests__/local-standalone.test.ts +36 -5
  20. package/src/__tests__/local-token-refresh.test.ts +361 -0
  21. package/src/__tests__/manage-secure-command-tool.test.ts +134 -0
  22. package/src/__tests__/managed-integration.test.ts +91 -112
  23. package/src/__tests__/managed-lazy-getters.test.ts +359 -0
  24. package/src/__tests__/managed-materializers.test.ts +1028 -0
  25. package/src/__tests__/managed-reconnect.test.ts +2 -2
  26. package/src/__tests__/managed-rejection.test.ts +43 -0
  27. package/src/__tests__/toolstore.test.ts +773 -0
  28. package/src/__tests__/transport.test.ts +27 -23
  29. package/src/audit/store.ts +188 -0
  30. package/src/cli.ts +1 -1
  31. package/src/commands/auth-adapters.ts +169 -0
  32. package/src/commands/egress-hooks.ts +203 -0
  33. package/src/commands/executor.ts +1155 -0
  34. package/src/commands/output-scan.ts +157 -0
  35. package/src/commands/profiles.ts +286 -0
  36. package/src/commands/validator.ts +702 -0
  37. package/src/commands/workspace.ts +550 -0
  38. package/src/grants/index.ts +17 -0
  39. package/src/grants/persistent-store.ts +309 -0
  40. package/src/grants/rpc-handlers.ts +293 -0
  41. package/src/grants/temporary-store.ts +289 -0
  42. package/src/http/audit.ts +84 -0
  43. package/src/http/executor.ts +684 -0
  44. package/src/http/path-template.ts +245 -0
  45. package/src/http/policy.ts +238 -0
  46. package/src/http/response-filter.ts +233 -0
  47. package/src/index.ts +88 -8
  48. package/src/main.ts +340 -228
  49. package/src/managed-errors.ts +9 -0
  50. package/src/managed-lazy-getters.ts +106 -0
  51. package/src/managed-main.ts +822 -0
  52. package/src/materializers/local-oauth-lookup.ts +98 -0
  53. package/src/materializers/local-token-refresh.ts +287 -0
  54. package/src/materializers/local.ts +316 -0
  55. package/src/materializers/managed-platform.ts +295 -0
  56. package/src/paths.ts +20 -4
  57. package/src/server.ts +469 -52
  58. package/src/subjects/local.ts +177 -0
  59. package/src/subjects/managed.ts +311 -0
  60. package/src/subjects/policy.ts +79 -0
  61. package/src/toolstore/integrity.ts +94 -0
  62. package/src/toolstore/manifest.ts +154 -0
  63. package/src/toolstore/publish.ts +571 -0
  64. package/node_modules/@vellumai/service-contracts/src/__tests__/attachment-naming.test.ts +0 -104
  65. package/node_modules/@vellumai/service-contracts/src/attachment-naming.ts +0 -118
@@ -0,0 +1,359 @@
1
+ /**
2
+ * Tests for the managed CES lazy API key getter pattern.
3
+ *
4
+ * Exercises the production `buildLazyGetters` from `managed-lazy-getters.ts`
5
+ * directly, ensuring regressions in key precedence, lazy resolution, or
6
+ * graceful degradation are caught by these tests.
7
+ */
8
+
9
+ import { describe, expect, test } from "bun:test";
10
+
11
+ import {
12
+ applyManagedCredentialRefs,
13
+ buildLazyGetters,
14
+ type ApiKeyRef,
15
+ type AssistantIdRef,
16
+ } from "../managed-lazy-getters.js";
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // applyManagedCredentialRefs — fail-closed overwrite across sessions
20
+ // ---------------------------------------------------------------------------
21
+
22
+ describe("applyManagedCredentialRefs", () => {
23
+ test("overwrites both refs with the provided values", () => {
24
+ const apiKeyRef: ApiKeyRef = { current: "old-key" };
25
+ const assistantIdRef: AssistantIdRef = { current: "ast_old" };
26
+
27
+ applyManagedCredentialRefs(apiKeyRef, assistantIdRef, "new-key", "ast_new");
28
+
29
+ expect(apiKeyRef.current).toBe("new-key");
30
+ expect(assistantIdRef.current).toBe("ast_new");
31
+ });
32
+
33
+ test("clears a stale assistant ID when the new value is omitted", () => {
34
+ // A new session (or an API-key-only update) that does not carry an
35
+ // assistant ID must not inherit the previous session's ID.
36
+ const apiKeyRef: ApiKeyRef = { current: "prev-key" };
37
+ const assistantIdRef: AssistantIdRef = { current: "ast_prev" };
38
+
39
+ applyManagedCredentialRefs(
40
+ apiKeyRef,
41
+ assistantIdRef,
42
+ "rotated-key",
43
+ undefined,
44
+ );
45
+
46
+ expect(apiKeyRef.current).toBe("rotated-key");
47
+ expect(assistantIdRef.current).toBe("");
48
+ });
49
+
50
+ test("clears a stale API key when the new value is omitted", () => {
51
+ const apiKeyRef: ApiKeyRef = { current: "prev-key" };
52
+ const assistantIdRef: AssistantIdRef = { current: "ast_prev" };
53
+
54
+ applyManagedCredentialRefs(apiKeyRef, assistantIdRef, undefined, undefined);
55
+
56
+ expect(apiKeyRef.current).toBe("");
57
+ expect(assistantIdRef.current).toBe("");
58
+ });
59
+
60
+ test("lazy getters fail closed after a reconnect that omits the ID", () => {
61
+ const apiKeyRef: ApiKeyRef = { current: "session1-key" };
62
+ const assistantIdRef: AssistantIdRef = { current: "ast_session1" };
63
+ const { getManagedMaterializerOptions } = buildLazyGetters({
64
+ platformBaseUrl: "https://api.vellum.ai",
65
+ assistantIdRef,
66
+ apiKeyRef,
67
+ });
68
+
69
+ // A reconnecting session provides a key but no assistant ID.
70
+ applyManagedCredentialRefs(
71
+ apiKeyRef,
72
+ assistantIdRef,
73
+ "session2-key",
74
+ undefined,
75
+ );
76
+
77
+ // Materialization must not proceed with the prior session's assistant ID.
78
+ expect(getManagedMaterializerOptions()).toBeUndefined();
79
+ });
80
+ });
81
+
82
+ // ---------------------------------------------------------------------------
83
+ // Before API key arrives
84
+ // ---------------------------------------------------------------------------
85
+
86
+ describe("managed lazy getters — before API key arrives", () => {
87
+ test("apiKeyRef starts empty and managed subject options are undefined", () => {
88
+ const apiKeyRef: ApiKeyRef = { current: "" };
89
+ const assistantIdRef: AssistantIdRef = { current: "ast_abc123" };
90
+ const { getManagedSubjectOptions } = buildLazyGetters({
91
+ platformBaseUrl: "https://api.vellum.ai",
92
+ assistantIdRef,
93
+ apiKeyRef,
94
+ });
95
+
96
+ expect(apiKeyRef.current).toBe("");
97
+ expect(getManagedSubjectOptions()).toBeUndefined();
98
+ });
99
+
100
+ test("apiKeyRef starts empty and managed materializer options are undefined", () => {
101
+ const apiKeyRef: ApiKeyRef = { current: "" };
102
+ const assistantIdRef: AssistantIdRef = { current: "ast_abc123" };
103
+ const { getManagedMaterializerOptions } = buildLazyGetters({
104
+ platformBaseUrl: "https://api.vellum.ai",
105
+ assistantIdRef,
106
+ apiKeyRef,
107
+ });
108
+
109
+ expect(getManagedMaterializerOptions()).toBeUndefined();
110
+ });
111
+
112
+ test("getAssistantApiKey returns empty string when ref is empty and no env var", () => {
113
+ const apiKeyRef: ApiKeyRef = { current: "" };
114
+ const assistantIdRef: AssistantIdRef = { current: "ast_abc123" };
115
+ const { getAssistantApiKey } = buildLazyGetters({
116
+ platformBaseUrl: "https://api.vellum.ai",
117
+ assistantIdRef,
118
+ apiKeyRef,
119
+ });
120
+
121
+ expect(getAssistantApiKey()).toBe("");
122
+ });
123
+ });
124
+
125
+ // ---------------------------------------------------------------------------
126
+ // After API key arrives via handshake
127
+ // ---------------------------------------------------------------------------
128
+
129
+ describe("managed lazy getters — after API key arrives via handshake", () => {
130
+ test("setting apiKeyRef.current enables managed subject options", () => {
131
+ const apiKeyRef: ApiKeyRef = { current: "" };
132
+ const assistantIdRef: AssistantIdRef = { current: "ast_abc123" };
133
+ const { getManagedSubjectOptions } = buildLazyGetters({
134
+ platformBaseUrl: "https://api.vellum.ai",
135
+ assistantIdRef,
136
+ apiKeyRef,
137
+ });
138
+
139
+ expect(getManagedSubjectOptions()).toBeUndefined();
140
+
141
+ apiKeyRef.current = "vak_test_key_12345";
142
+
143
+ const opts = getManagedSubjectOptions();
144
+ expect(opts).toBeDefined();
145
+ expect(opts!.platformBaseUrl).toBe("https://api.vellum.ai");
146
+ expect(opts!.assistantApiKey).toBe("vak_test_key_12345");
147
+ expect(opts!.assistantId).toBe("ast_abc123");
148
+ });
149
+
150
+ test("setting apiKeyRef.current enables managed materializer options", () => {
151
+ const apiKeyRef: ApiKeyRef = { current: "" };
152
+ const assistantIdRef: AssistantIdRef = { current: "ast_abc123" };
153
+ const { getManagedMaterializerOptions } = buildLazyGetters({
154
+ platformBaseUrl: "https://api.vellum.ai",
155
+ assistantIdRef,
156
+ apiKeyRef,
157
+ });
158
+
159
+ expect(getManagedMaterializerOptions()).toBeUndefined();
160
+
161
+ apiKeyRef.current = "vak_test_key_12345";
162
+
163
+ const opts = getManagedMaterializerOptions();
164
+ expect(opts).toBeDefined();
165
+ expect(opts!.platformBaseUrl).toBe("https://api.vellum.ai");
166
+ expect(opts!.assistantApiKey).toBe("vak_test_key_12345");
167
+ expect(opts!.assistantId).toBe("ast_abc123");
168
+ });
169
+
170
+ test("returned options contain the exact key from the ref (not a stale copy)", () => {
171
+ const apiKeyRef: ApiKeyRef = { current: "" };
172
+ const assistantIdRef: AssistantIdRef = { current: "ast_abc123" };
173
+ const { getManagedSubjectOptions, getManagedMaterializerOptions } =
174
+ buildLazyGetters({
175
+ platformBaseUrl: "https://api.vellum.ai",
176
+ assistantIdRef,
177
+ apiKeyRef,
178
+ });
179
+
180
+ apiKeyRef.current = "vak_key_v1";
181
+ expect(getManagedSubjectOptions()!.assistantApiKey).toBe("vak_key_v1");
182
+ expect(getManagedMaterializerOptions()!.assistantApiKey).toBe("vak_key_v1");
183
+
184
+ apiKeyRef.current = "vak_key_v2";
185
+ expect(getManagedSubjectOptions()!.assistantApiKey).toBe("vak_key_v2");
186
+ expect(getManagedMaterializerOptions()!.assistantApiKey).toBe("vak_key_v2");
187
+ });
188
+ });
189
+
190
+ // ---------------------------------------------------------------------------
191
+ // Lazy resolution timing
192
+ // ---------------------------------------------------------------------------
193
+
194
+ describe("managed lazy getters — lazy resolution timing", () => {
195
+ test("handlers built before key arrives resolve the key at call time", () => {
196
+ const apiKeyRef: ApiKeyRef = { current: "" };
197
+ const assistantIdRef: AssistantIdRef = { current: "ast_abc123" };
198
+
199
+ const { getManagedSubjectOptions, getManagedMaterializerOptions } =
200
+ buildLazyGetters({
201
+ platformBaseUrl: "https://api.vellum.ai",
202
+ assistantIdRef,
203
+ apiKeyRef,
204
+ });
205
+
206
+ // At registration time: no key yet
207
+ expect(getManagedSubjectOptions()).toBeUndefined();
208
+ expect(getManagedMaterializerOptions()).toBeUndefined();
209
+
210
+ // Later: handshake delivers the key
211
+ apiKeyRef.current = "vak_late_arriving_key";
212
+
213
+ // Same getter functions now return valid options
214
+ expect(getManagedSubjectOptions()).toBeDefined();
215
+ expect(getManagedMaterializerOptions()).toBeDefined();
216
+ expect(getManagedSubjectOptions()!.assistantApiKey).toBe(
217
+ "vak_late_arriving_key",
218
+ );
219
+ });
220
+
221
+ test("deps object with getter properties resolves lazily (mirrors httpDeps pattern)", () => {
222
+ const apiKeyRef: ApiKeyRef = { current: "" };
223
+ const assistantIdRef: AssistantIdRef = { current: "ast_abc123" };
224
+ const { getManagedSubjectOptions, getManagedMaterializerOptions } =
225
+ buildLazyGetters({
226
+ platformBaseUrl: "https://api.vellum.ai",
227
+ assistantIdRef,
228
+ apiKeyRef,
229
+ });
230
+
231
+ // Build a deps object with getters, mirroring managed-main.ts httpDeps
232
+ const httpDeps = {
233
+ get managedSubjectOptions() {
234
+ return getManagedSubjectOptions();
235
+ },
236
+ get managedMaterializerOptions() {
237
+ return getManagedMaterializerOptions();
238
+ },
239
+ };
240
+
241
+ // Before key: both undefined
242
+ expect(httpDeps.managedSubjectOptions).toBeUndefined();
243
+ expect(httpDeps.managedMaterializerOptions).toBeUndefined();
244
+
245
+ // After key: both resolved
246
+ apiKeyRef.current = "vak_lazy_key";
247
+ expect(httpDeps.managedSubjectOptions).toBeDefined();
248
+ expect(httpDeps.managedSubjectOptions!.assistantApiKey).toBe(
249
+ "vak_lazy_key",
250
+ );
251
+ expect(httpDeps.managedMaterializerOptions).toBeDefined();
252
+ expect(httpDeps.managedMaterializerOptions!.assistantApiKey).toBe(
253
+ "vak_lazy_key",
254
+ );
255
+ });
256
+
257
+ test("env var fallback is used when ref is empty", () => {
258
+ const apiKeyRef: ApiKeyRef = { current: "" };
259
+ const assistantIdRef: AssistantIdRef = { current: "ast_abc123" };
260
+ const { getAssistantApiKey, getManagedSubjectOptions } = buildLazyGetters({
261
+ platformBaseUrl: "https://api.vellum.ai",
262
+ assistantIdRef,
263
+ apiKeyRef,
264
+ envApiKey: "vak_env_fallback",
265
+ });
266
+
267
+ expect(getAssistantApiKey()).toBe("vak_env_fallback");
268
+ expect(getManagedSubjectOptions()).toBeDefined();
269
+ expect(getManagedSubjectOptions()!.assistantApiKey).toBe(
270
+ "vak_env_fallback",
271
+ );
272
+ });
273
+
274
+ test("handshake-provided key takes precedence over env var", () => {
275
+ const apiKeyRef: ApiKeyRef = { current: "" };
276
+ const assistantIdRef: AssistantIdRef = { current: "ast_abc123" };
277
+ const { getAssistantApiKey } = buildLazyGetters({
278
+ platformBaseUrl: "https://api.vellum.ai",
279
+ assistantIdRef,
280
+ apiKeyRef,
281
+ envApiKey: "vak_env_key",
282
+ });
283
+
284
+ expect(getAssistantApiKey()).toBe("vak_env_key");
285
+
286
+ apiKeyRef.current = "vak_handshake_key";
287
+ expect(getAssistantApiKey()).toBe("vak_handshake_key");
288
+ });
289
+ });
290
+
291
+ // ---------------------------------------------------------------------------
292
+ // Missing required fields -> undefined (graceful degradation)
293
+ // ---------------------------------------------------------------------------
294
+
295
+ describe("managed lazy getters — missing platform config fields", () => {
296
+ test("missing platformBaseUrl returns undefined even with API key", () => {
297
+ const apiKeyRef: ApiKeyRef = { current: "vak_test_key" };
298
+ const assistantIdRef: AssistantIdRef = { current: "ast_abc123" };
299
+ const { getManagedSubjectOptions, getManagedMaterializerOptions } =
300
+ buildLazyGetters({
301
+ platformBaseUrl: "",
302
+ assistantIdRef,
303
+ apiKeyRef,
304
+ });
305
+
306
+ expect(getManagedSubjectOptions()).toBeUndefined();
307
+ expect(getManagedMaterializerOptions()).toBeUndefined();
308
+ });
309
+
310
+ test("missing assistantId returns undefined even with API key", () => {
311
+ const apiKeyRef: ApiKeyRef = { current: "vak_test_key" };
312
+ const assistantIdRef: AssistantIdRef = { current: "" };
313
+ const { getManagedSubjectOptions, getManagedMaterializerOptions } =
314
+ buildLazyGetters({
315
+ platformBaseUrl: "https://api.vellum.ai",
316
+ assistantIdRef,
317
+ apiKeyRef,
318
+ });
319
+
320
+ expect(getManagedSubjectOptions()).toBeUndefined();
321
+ expect(getManagedMaterializerOptions()).toBeUndefined();
322
+ });
323
+
324
+ test("assistantIdRef updated after build enables options (warm-pool scenario)", () => {
325
+ /**
326
+ * Verifies that updating assistantIdRef.current after buildLazyGetters
327
+ * makes previously-undefined options become defined — the core fix for
328
+ * warm-pool pods where the assistant ID is empty at CES startup.
329
+ */
330
+
331
+ // GIVEN an API key is available but assistant ID is empty (warm-pool startup)
332
+ const apiKeyRef: ApiKeyRef = { current: "vak_test_key" };
333
+ const assistantIdRef: AssistantIdRef = { current: "" };
334
+ const { getManagedSubjectOptions, getManagedMaterializerOptions } =
335
+ buildLazyGetters({
336
+ platformBaseUrl: "https://api.vellum.ai",
337
+ assistantIdRef,
338
+ apiKeyRef,
339
+ });
340
+
341
+ // WHEN options are checked before assistant ID arrives
342
+ // THEN they are undefined
343
+ expect(getManagedSubjectOptions()).toBeUndefined();
344
+ expect(getManagedMaterializerOptions()).toBeUndefined();
345
+
346
+ // WHEN the assistant ID arrives via handshake/RPC
347
+ assistantIdRef.current = "ast_provisioned_123";
348
+
349
+ // THEN the same getter functions now return valid options
350
+ const subOpts = getManagedSubjectOptions();
351
+ expect(subOpts).toBeDefined();
352
+ expect(subOpts!.assistantId).toBe("ast_provisioned_123");
353
+ expect(subOpts!.assistantApiKey).toBe("vak_test_key");
354
+
355
+ const matOpts = getManagedMaterializerOptions();
356
+ expect(matOpts).toBeDefined();
357
+ expect(matOpts!.assistantId).toBe("ast_provisioned_123");
358
+ });
359
+ });