@vellumai/credential-executor 0.10.7 → 0.10.8-dev.202607102228.5945895
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.
- package/Dockerfile +1 -1
- package/node_modules/@vellumai/service-contracts/package.json +1 -2
- package/node_modules/@vellumai/service-contracts/src/__tests__/attachment-naming.test.ts +104 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +0 -2
- package/node_modules/@vellumai/service-contracts/src/attachment-naming.ts +118 -0
- package/node_modules/@vellumai/service-contracts/src/credential-rpc.ts +3 -5
- package/node_modules/@vellumai/service-contracts/src/index.ts +2 -4
- package/node_modules/@vellumai/service-contracts/src/rpc.ts +4 -447
- package/package.json +2 -3
- package/src/__tests__/bulk-set-credentials.test.ts +1 -1
- package/src/__tests__/local-standalone.test.ts +5 -36
- package/src/__tests__/managed-integration.test.ts +112 -91
- package/src/__tests__/managed-reconnect.test.ts +2 -2
- package/src/__tests__/transport.test.ts +23 -27
- package/src/cli.ts +1 -1
- package/src/index.ts +8 -88
- package/src/main.ts +228 -340
- package/src/paths.ts +4 -20
- package/src/server.ts +52 -469
- package/node_modules/@vellumai/service-contracts/src/__tests__/grants.test.ts +0 -686
- package/node_modules/@vellumai/service-contracts/src/grants.ts +0 -184
- package/node_modules/@vellumai/service-contracts/src/rendering.ts +0 -135
- package/src/__tests__/command-executor.test.ts +0 -1879
- package/src/__tests__/command-validator.test.ts +0 -1405
- package/src/__tests__/command-workspace.test.ts +0 -1050
- package/src/__tests__/grant-store.test.ts +0 -689
- package/src/__tests__/http-executor.test.ts +0 -1336
- package/src/__tests__/http-policy.test.ts +0 -1069
- package/src/__tests__/local-materializers.test.ts +0 -860
- package/src/__tests__/local-token-refresh.test.ts +0 -361
- package/src/__tests__/manage-secure-command-tool.test.ts +0 -134
- package/src/__tests__/managed-lazy-getters.test.ts +0 -359
- package/src/__tests__/managed-materializers.test.ts +0 -1028
- package/src/__tests__/managed-rejection.test.ts +0 -43
- package/src/__tests__/toolstore.test.ts +0 -773
- package/src/audit/store.ts +0 -188
- package/src/commands/auth-adapters.ts +0 -169
- package/src/commands/egress-hooks.ts +0 -203
- package/src/commands/executor.ts +0 -1155
- package/src/commands/output-scan.ts +0 -157
- package/src/commands/profiles.ts +0 -286
- package/src/commands/validator.ts +0 -702
- package/src/commands/workspace.ts +0 -550
- package/src/grants/index.ts +0 -17
- package/src/grants/persistent-store.ts +0 -309
- package/src/grants/rpc-handlers.ts +0 -293
- package/src/grants/temporary-store.ts +0 -289
- package/src/http/audit.ts +0 -84
- package/src/http/executor.ts +0 -684
- package/src/http/path-template.ts +0 -245
- package/src/http/policy.ts +0 -238
- package/src/http/response-filter.ts +0 -233
- package/src/managed-errors.ts +0 -9
- package/src/managed-lazy-getters.ts +0 -106
- package/src/managed-main.ts +0 -822
- package/src/materializers/local-oauth-lookup.ts +0 -98
- package/src/materializers/local-token-refresh.ts +0 -287
- package/src/materializers/local.ts +0 -316
- package/src/materializers/managed-platform.ts +0 -295
- package/src/subjects/local.ts +0 -177
- package/src/subjects/managed.ts +0 -311
- package/src/subjects/policy.ts +0 -79
- package/src/toolstore/integrity.ts +0 -94
- package/src/toolstore/manifest.ts +0 -154
- package/src/toolstore/publish.ts +0 -571
|
@@ -21,14 +21,17 @@ import { afterEach, describe, expect, test } from "bun:test";
|
|
|
21
21
|
import { mkdirSync, mkdtempSync, rmSync, unlinkSync } from "node:fs";
|
|
22
22
|
import { tmpdir } from "node:os";
|
|
23
23
|
import { join } from "node:path";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
createConnection,
|
|
26
|
+
createServer as createNetServer,
|
|
27
|
+
type Socket,
|
|
28
|
+
} from "node:net";
|
|
25
29
|
import { Readable, Writable } from "node:stream";
|
|
26
30
|
|
|
27
31
|
import {
|
|
28
32
|
CES_PROTOCOL_VERSION,
|
|
29
33
|
CesRpcMethod,
|
|
30
34
|
type HandshakeAck,
|
|
31
|
-
type ListGrantsResponse,
|
|
32
35
|
type GetCredentialResponse,
|
|
33
36
|
type SetCredentialResponse,
|
|
34
37
|
type DeleteCredentialResponse,
|
|
@@ -36,13 +39,11 @@ import {
|
|
|
36
39
|
type RpcEnvelope,
|
|
37
40
|
} from "@vellumai/service-contracts/credential-rpc";
|
|
38
41
|
|
|
39
|
-
import { PersistentGrantStore } from "../grants/persistent-store.js";
|
|
40
|
-
import { AuditStore } from "../audit/store.js";
|
|
41
42
|
import {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
CesRpcServer,
|
|
44
|
+
type RpcHandlerRegistry,
|
|
45
|
+
type ServeEndReason,
|
|
46
|
+
} from "../server.js";
|
|
46
47
|
import { createLocalSecureKeyBackend } from "../materializers/local-secure-key-backend.js";
|
|
47
48
|
|
|
48
49
|
// ---------------------------------------------------------------------------
|
|
@@ -83,40 +84,11 @@ function restoreEnv(saved: SavedEnv): void {
|
|
|
83
84
|
// Helpers
|
|
84
85
|
// ---------------------------------------------------------------------------
|
|
85
86
|
|
|
86
|
-
/**
|
|
87
|
-
* Build a minimal RPC handler registry with `list_grants` and
|
|
88
|
-
* `list_audit_records` backed by real stores in a temp directory.
|
|
89
|
-
*/
|
|
90
|
-
function buildMinimalHandlers(dataDir: string): RpcHandlerRegistry {
|
|
91
|
-
const grantsDir = join(dataDir, "grants");
|
|
92
|
-
const auditDir = join(dataDir, "audit");
|
|
93
|
-
mkdirSync(grantsDir, { recursive: true });
|
|
94
|
-
mkdirSync(auditDir, { recursive: true });
|
|
95
|
-
|
|
96
|
-
const persistentGrantStore = new PersistentGrantStore(grantsDir);
|
|
97
|
-
persistentGrantStore.init();
|
|
98
|
-
|
|
99
|
-
const auditStore = new AuditStore(auditDir);
|
|
100
|
-
auditStore.init();
|
|
101
|
-
|
|
102
|
-
const handlers: RpcHandlerRegistry = {};
|
|
103
|
-
|
|
104
|
-
handlers[CesRpcMethod.ListGrants] = createListGrantsHandler({
|
|
105
|
-
persistentGrantStore,
|
|
106
|
-
}) as typeof handlers[string];
|
|
107
|
-
|
|
108
|
-
handlers[CesRpcMethod.ListAuditRecords] = createListAuditRecordsHandler({
|
|
109
|
-
auditStore,
|
|
110
|
-
}) as typeof handlers[string];
|
|
111
|
-
|
|
112
|
-
return handlers;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
87
|
/**
|
|
116
88
|
* Build an RPC handler registry with credential CRUD handlers backed by
|
|
117
89
|
* a real SecureKeyBackend using a temp directory for credential storage.
|
|
118
90
|
*
|
|
119
|
-
* Mirrors the handler registration in
|
|
91
|
+
* Mirrors the handler registration in main.ts.
|
|
120
92
|
*/
|
|
121
93
|
function buildCredentialHandlers(vellumRoot: string): RpcHandlerRegistry {
|
|
122
94
|
const secureKeyBackend = createLocalSecureKeyBackend(vellumRoot);
|
|
@@ -125,22 +97,27 @@ function buildCredentialHandlers(vellumRoot: string): RpcHandlerRegistry {
|
|
|
125
97
|
handlers[CesRpcMethod.GetCredential] = (async (req: { account: string }) => {
|
|
126
98
|
const value = await secureKeyBackend.get(req.account);
|
|
127
99
|
return { found: value !== undefined, value };
|
|
128
|
-
}) as typeof handlers[string];
|
|
100
|
+
}) as (typeof handlers)[string];
|
|
129
101
|
|
|
130
|
-
handlers[CesRpcMethod.SetCredential] = (async (req: {
|
|
102
|
+
handlers[CesRpcMethod.SetCredential] = (async (req: {
|
|
103
|
+
account: string;
|
|
104
|
+
value: string;
|
|
105
|
+
}) => {
|
|
131
106
|
const ok = await secureKeyBackend.set(req.account, req.value);
|
|
132
107
|
return { ok };
|
|
133
|
-
}) as typeof handlers[string];
|
|
108
|
+
}) as (typeof handlers)[string];
|
|
134
109
|
|
|
135
|
-
handlers[CesRpcMethod.DeleteCredential] = (async (req: {
|
|
110
|
+
handlers[CesRpcMethod.DeleteCredential] = (async (req: {
|
|
111
|
+
account: string;
|
|
112
|
+
}) => {
|
|
136
113
|
const result = await secureKeyBackend.delete(req.account);
|
|
137
114
|
return { result };
|
|
138
|
-
}) as typeof handlers[string];
|
|
115
|
+
}) as (typeof handlers)[string];
|
|
139
116
|
|
|
140
117
|
handlers[CesRpcMethod.ListCredentials] = (async () => {
|
|
141
118
|
const accounts = await secureKeyBackend.list();
|
|
142
119
|
return { accounts };
|
|
143
|
-
}) as typeof handlers[string];
|
|
120
|
+
}) as (typeof handlers)[string];
|
|
144
121
|
|
|
145
122
|
return handlers;
|
|
146
123
|
}
|
|
@@ -149,10 +126,13 @@ function buildCredentialHandlers(vellumRoot: string): RpcHandlerRegistry {
|
|
|
149
126
|
* Accept a single connection on a Unix socket and return
|
|
150
127
|
* readable/writable streams plus cleanup helpers.
|
|
151
128
|
*
|
|
152
|
-
* Replicates the same accept-one-connection pattern from
|
|
129
|
+
* Replicates the same accept-one-connection pattern from main.ts
|
|
153
130
|
* but in a test-friendly form.
|
|
154
131
|
*/
|
|
155
|
-
function acceptOneConnection(
|
|
132
|
+
function acceptOneConnection(
|
|
133
|
+
socketPath: string,
|
|
134
|
+
signal: AbortSignal,
|
|
135
|
+
): Promise<{
|
|
156
136
|
readable: Readable;
|
|
157
137
|
writable: Writable;
|
|
158
138
|
socket: Socket;
|
|
@@ -162,7 +142,11 @@ function acceptOneConnection(socketPath: string, signal: AbortSignal): Promise<{
|
|
|
162
142
|
|
|
163
143
|
const cleanup = () => {
|
|
164
144
|
netServer.close();
|
|
165
|
-
try {
|
|
145
|
+
try {
|
|
146
|
+
unlinkSync(socketPath);
|
|
147
|
+
} catch {
|
|
148
|
+
/* ok */
|
|
149
|
+
}
|
|
166
150
|
};
|
|
167
151
|
|
|
168
152
|
if (signal.aborted) {
|
|
@@ -170,10 +154,14 @@ function acceptOneConnection(socketPath: string, signal: AbortSignal): Promise<{
|
|
|
170
154
|
return;
|
|
171
155
|
}
|
|
172
156
|
|
|
173
|
-
signal.addEventListener(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
157
|
+
signal.addEventListener(
|
|
158
|
+
"abort",
|
|
159
|
+
() => {
|
|
160
|
+
cleanup();
|
|
161
|
+
reject(new Error("Aborted while waiting for connection"));
|
|
162
|
+
},
|
|
163
|
+
{ once: true },
|
|
164
|
+
);
|
|
177
165
|
|
|
178
166
|
netServer.on("error", (err: Error) => {
|
|
179
167
|
cleanup();
|
|
@@ -186,11 +174,19 @@ function acceptOneConnection(socketPath: string, signal: AbortSignal): Promise<{
|
|
|
186
174
|
|
|
187
175
|
netServer.on("connection", (sock: Socket) => {
|
|
188
176
|
netServer.close();
|
|
189
|
-
try {
|
|
177
|
+
try {
|
|
178
|
+
unlinkSync(socketPath);
|
|
179
|
+
} catch {
|
|
180
|
+
/* ok */
|
|
181
|
+
}
|
|
190
182
|
|
|
191
183
|
const readable = new Readable({ read() {} });
|
|
192
184
|
const writable = new Writable({
|
|
193
|
-
write(
|
|
185
|
+
write(
|
|
186
|
+
chunk: Buffer,
|
|
187
|
+
_encoding: string,
|
|
188
|
+
callback: (err?: Error | null) => void,
|
|
189
|
+
) {
|
|
194
190
|
if (sock.writable) {
|
|
195
191
|
sock.write(chunk, callback);
|
|
196
192
|
} else {
|
|
@@ -256,7 +252,11 @@ function connectToSocket(
|
|
|
256
252
|
* Read newline-delimited JSON messages from a socket, collecting them
|
|
257
253
|
* until the expected count is reached or a timeout fires.
|
|
258
254
|
*/
|
|
259
|
-
function readMessages(
|
|
255
|
+
function readMessages(
|
|
256
|
+
sock: Socket,
|
|
257
|
+
expectedCount: number,
|
|
258
|
+
timeoutMs = 3000,
|
|
259
|
+
): Promise<unknown[]> {
|
|
260
260
|
return new Promise((resolve, reject) => {
|
|
261
261
|
const messages: unknown[] = [];
|
|
262
262
|
let buffer = "";
|
|
@@ -329,7 +329,9 @@ afterEach(async () => {
|
|
|
329
329
|
if (tmpDir) {
|
|
330
330
|
try {
|
|
331
331
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
332
|
-
} catch {
|
|
332
|
+
} catch {
|
|
333
|
+
/* ok */
|
|
334
|
+
}
|
|
333
335
|
}
|
|
334
336
|
});
|
|
335
337
|
|
|
@@ -367,7 +369,7 @@ describe("managed CES integration (real Unix socket)", () => {
|
|
|
367
369
|
|
|
368
370
|
// -- Start health server ---------------------------------------------------
|
|
369
371
|
// NOTE: This uses a local Bun.serve mock rather than the production
|
|
370
|
-
// `startHealthServer()` from
|
|
372
|
+
// `startHealthServer()` from main.ts. The production function is
|
|
371
373
|
// not exported and depends on module-level mutable state (the
|
|
372
374
|
// `rpcConnected` flag) that cannot be controlled from tests.
|
|
373
375
|
//
|
|
@@ -377,7 +379,7 @@ describe("managed CES integration (real Unix socket)", () => {
|
|
|
377
379
|
// - End-to-end socket + RPC + health plumbing in one integration flow.
|
|
378
380
|
//
|
|
379
381
|
// What this does NOT cover:
|
|
380
|
-
// - The actual `startHealthServer()` implementation in
|
|
382
|
+
// - The actual `startHealthServer()` implementation in main.ts,
|
|
381
383
|
// including the `rpcConnected` field in the /readyz response. If the
|
|
382
384
|
// production health handler drifts (changes routes, status codes, or
|
|
383
385
|
// response shape), this test will not catch it. To close that gap,
|
|
@@ -388,10 +390,9 @@ describe("managed CES integration (real Unix socket)", () => {
|
|
|
388
390
|
fetch(req) {
|
|
389
391
|
const url = new URL(req.url);
|
|
390
392
|
if (url.pathname === "/healthz") {
|
|
391
|
-
return new Response(
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
);
|
|
393
|
+
return new Response(JSON.stringify({ status: "ok" }), {
|
|
394
|
+
headers: { "Content-Type": "application/json" },
|
|
395
|
+
});
|
|
395
396
|
}
|
|
396
397
|
if (url.pathname === "/readyz") {
|
|
397
398
|
return new Response(
|
|
@@ -404,7 +405,10 @@ describe("managed CES integration (real Unix socket)", () => {
|
|
|
404
405
|
});
|
|
405
406
|
|
|
406
407
|
// -- Start accept-one-connection on the Unix socket ------------------------
|
|
407
|
-
const connectionPromise = acceptOneConnection(
|
|
408
|
+
const connectionPromise = acceptOneConnection(
|
|
409
|
+
socketPath,
|
|
410
|
+
controller.signal,
|
|
411
|
+
);
|
|
408
412
|
|
|
409
413
|
// -- Client connects -------------------------------------------------------
|
|
410
414
|
clientSocket = await connectToSocket(socketPath);
|
|
@@ -413,7 +417,7 @@ describe("managed CES integration (real Unix socket)", () => {
|
|
|
413
417
|
const conn = await connectionPromise;
|
|
414
418
|
|
|
415
419
|
let observedSessionId = "";
|
|
416
|
-
const handlers =
|
|
420
|
+
const handlers = buildCredentialHandlers(dataDir);
|
|
417
421
|
|
|
418
422
|
serverRpcServer = new CesRpcServer({
|
|
419
423
|
input: conn.readable,
|
|
@@ -454,13 +458,13 @@ describe("managed CES integration (real Unix socket)", () => {
|
|
|
454
458
|
expect(observedSessionId).toBe(handshakeSessionId);
|
|
455
459
|
expect(serverRpcServer.currentSessionId).toBe(handshakeSessionId);
|
|
456
460
|
|
|
457
|
-
// -- Step 2: RPC dispatch (
|
|
461
|
+
// -- Step 2: RPC dispatch (list_credentials) -------------------------------
|
|
458
462
|
const rpcId = "rpc-1";
|
|
459
463
|
sendMessage(clientSocket, {
|
|
460
464
|
type: "rpc",
|
|
461
465
|
id: rpcId,
|
|
462
466
|
kind: "request",
|
|
463
|
-
method: CesRpcMethod.
|
|
467
|
+
method: CesRpcMethod.ListCredentials,
|
|
464
468
|
payload: {},
|
|
465
469
|
timestamp: new Date().toISOString(),
|
|
466
470
|
});
|
|
@@ -472,34 +476,33 @@ describe("managed CES integration (real Unix socket)", () => {
|
|
|
472
476
|
expect(rpcResp.type).toBe("rpc");
|
|
473
477
|
expect(rpcResp.id).toBe(rpcId);
|
|
474
478
|
expect(rpcResp.kind).toBe("response");
|
|
475
|
-
expect(rpcResp.method).toBe(CesRpcMethod.
|
|
479
|
+
expect(rpcResp.method).toBe(CesRpcMethod.ListCredentials);
|
|
476
480
|
|
|
477
|
-
const
|
|
478
|
-
expect(
|
|
481
|
+
const listPayload = rpcResp.payload as { accounts: string[] };
|
|
482
|
+
expect(listPayload.accounts).toEqual([]);
|
|
479
483
|
|
|
480
|
-
// -- Step 3: RPC dispatch (
|
|
481
|
-
const
|
|
484
|
+
// -- Step 3: RPC dispatch (get_credential, not found) ----------------------
|
|
485
|
+
const getRpcId = "rpc-2";
|
|
482
486
|
sendMessage(clientSocket, {
|
|
483
487
|
type: "rpc",
|
|
484
|
-
id:
|
|
488
|
+
id: getRpcId,
|
|
485
489
|
kind: "request",
|
|
486
|
-
method: CesRpcMethod.
|
|
487
|
-
payload: {},
|
|
490
|
+
method: CesRpcMethod.GetCredential,
|
|
491
|
+
payload: { account: "does-not-exist" },
|
|
488
492
|
timestamp: new Date().toISOString(),
|
|
489
493
|
});
|
|
490
494
|
|
|
491
|
-
const
|
|
492
|
-
expect(
|
|
495
|
+
const getMessages = await readMessages(clientSocket, 1);
|
|
496
|
+
expect(getMessages.length).toBe(1);
|
|
493
497
|
|
|
494
|
-
const
|
|
495
|
-
expect(
|
|
496
|
-
expect(
|
|
497
|
-
expect(
|
|
498
|
-
expect(
|
|
498
|
+
const getResp = getMessages[0] as RpcEnvelope & { type: "rpc" };
|
|
499
|
+
expect(getResp.type).toBe("rpc");
|
|
500
|
+
expect(getResp.id).toBe(getRpcId);
|
|
501
|
+
expect(getResp.kind).toBe("response");
|
|
502
|
+
expect(getResp.method).toBe(CesRpcMethod.GetCredential);
|
|
499
503
|
|
|
500
|
-
const
|
|
501
|
-
expect(
|
|
502
|
-
expect(auditPayload.nextCursor).toBeNull();
|
|
504
|
+
const getPayload = getResp.payload as { found: boolean };
|
|
505
|
+
expect(getPayload.found).toBe(false);
|
|
503
506
|
|
|
504
507
|
// -- Step 4: Health endpoint -----------------------------------------------
|
|
505
508
|
const healthzResp = await fetch(`http://localhost:${healthPort}/healthz`);
|
|
@@ -529,7 +532,10 @@ describe("managed CES integration (real Unix socket)", () => {
|
|
|
529
532
|
const unknownResp = unknownMessages[0] as RpcEnvelope & { type: "rpc" };
|
|
530
533
|
expect(unknownResp.id).toBe(unknownRpcId);
|
|
531
534
|
expect(unknownResp.kind).toBe("response");
|
|
532
|
-
const unknownPayload = unknownResp.payload as {
|
|
535
|
+
const unknownPayload = unknownResp.payload as {
|
|
536
|
+
success: boolean;
|
|
537
|
+
error: { code: string };
|
|
538
|
+
};
|
|
533
539
|
expect(unknownPayload.error.code).toBe("METHOD_NOT_FOUND");
|
|
534
540
|
|
|
535
541
|
// -- Cleanup ---------------------------------------------------------------
|
|
@@ -552,7 +558,10 @@ describe("managed CES integration (real Unix socket)", () => {
|
|
|
552
558
|
|
|
553
559
|
controller = new AbortController();
|
|
554
560
|
|
|
555
|
-
const connectionPromise = acceptOneConnection(
|
|
561
|
+
const connectionPromise = acceptOneConnection(
|
|
562
|
+
socketPath,
|
|
563
|
+
controller.signal,
|
|
564
|
+
);
|
|
556
565
|
clientSocket = await connectToSocket(socketPath);
|
|
557
566
|
const conn = await connectionPromise;
|
|
558
567
|
|
|
@@ -601,11 +610,14 @@ describe("managed CES integration (real Unix socket)", () => {
|
|
|
601
610
|
|
|
602
611
|
controller = new AbortController();
|
|
603
612
|
|
|
604
|
-
const connectionPromise = acceptOneConnection(
|
|
613
|
+
const connectionPromise = acceptOneConnection(
|
|
614
|
+
socketPath,
|
|
615
|
+
controller.signal,
|
|
616
|
+
);
|
|
605
617
|
clientSocket = await connectToSocket(socketPath);
|
|
606
618
|
const conn = await connectionPromise;
|
|
607
619
|
|
|
608
|
-
const handlers =
|
|
620
|
+
const handlers = buildCredentialHandlers(dataDir);
|
|
609
621
|
serverRpcServer = new CesRpcServer({
|
|
610
622
|
input: conn.readable,
|
|
611
623
|
output: conn.writable,
|
|
@@ -621,7 +633,7 @@ describe("managed CES integration (real Unix socket)", () => {
|
|
|
621
633
|
type: "rpc",
|
|
622
634
|
id: "pre-hs-1",
|
|
623
635
|
kind: "request",
|
|
624
|
-
method: CesRpcMethod.
|
|
636
|
+
method: CesRpcMethod.ListCredentials,
|
|
625
637
|
payload: {},
|
|
626
638
|
timestamp: new Date().toISOString(),
|
|
627
639
|
});
|
|
@@ -632,7 +644,10 @@ describe("managed CES integration (real Unix socket)", () => {
|
|
|
632
644
|
const resp = messages[0] as RpcEnvelope & { type: "rpc" };
|
|
633
645
|
expect(resp.id).toBe("pre-hs-1");
|
|
634
646
|
expect(resp.kind).toBe("response");
|
|
635
|
-
const payload = resp.payload as {
|
|
647
|
+
const payload = resp.payload as {
|
|
648
|
+
success: boolean;
|
|
649
|
+
error: { code: string };
|
|
650
|
+
};
|
|
636
651
|
expect(payload.error.code).toBe("HANDSHAKE_REQUIRED");
|
|
637
652
|
|
|
638
653
|
clientSocket.end();
|
|
@@ -649,7 +664,10 @@ describe("managed CES integration (real Unix socket)", () => {
|
|
|
649
664
|
|
|
650
665
|
controller = new AbortController();
|
|
651
666
|
|
|
652
|
-
const connectionPromise = acceptOneConnection(
|
|
667
|
+
const connectionPromise = acceptOneConnection(
|
|
668
|
+
socketPath,
|
|
669
|
+
controller.signal,
|
|
670
|
+
);
|
|
653
671
|
|
|
654
672
|
// First connection succeeds
|
|
655
673
|
clientSocket = await connectToSocket(socketPath);
|
|
@@ -700,7 +718,10 @@ describe("credential CRUD RPC", () => {
|
|
|
700
718
|
|
|
701
719
|
controller = new AbortController();
|
|
702
720
|
|
|
703
|
-
const connectionPromise = acceptOneConnection(
|
|
721
|
+
const connectionPromise = acceptOneConnection(
|
|
722
|
+
socketPath,
|
|
723
|
+
controller.signal,
|
|
724
|
+
);
|
|
704
725
|
clientSocket = await connectToSocket(socketPath);
|
|
705
726
|
const conn = await connectionPromise;
|
|
706
727
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Managed CES reconnection test (real entrypoint subprocess).
|
|
3
3
|
*
|
|
4
|
-
* Spawns the actual `
|
|
4
|
+
* Spawns the actual `main.ts` entrypoint and verifies that the CES
|
|
5
5
|
* sidecar survives the assistant disconnecting and accepts a reconnection,
|
|
6
6
|
* rather than shutting down when the RPC stream ends.
|
|
7
7
|
*
|
|
@@ -179,7 +179,7 @@ describe("managed CES reconnection (real entrypoint)", () => {
|
|
|
179
179
|
mkdirSync(join(assistantDataMount, ".vellum"), { recursive: true });
|
|
180
180
|
|
|
181
181
|
const healthPort = await pickFreePort();
|
|
182
|
-
const managedMain = resolve(__dirname, "..", "
|
|
182
|
+
const managedMain = resolve(__dirname, "..", "main.ts");
|
|
183
183
|
|
|
184
184
|
proc = Bun.spawn({
|
|
185
185
|
cmd: [process.execPath, managedMain],
|
|
@@ -125,39 +125,28 @@ function createTestServer(handlers: RpcHandlerRegistry = {}) {
|
|
|
125
125
|
// ---------------------------------------------------------------------------
|
|
126
126
|
|
|
127
127
|
describe("managed entrypoint transport isolation", () => {
|
|
128
|
-
test("
|
|
128
|
+
test("main.ts uses Bun.serve only for health probes (managed mode)", () => {
|
|
129
129
|
const src = readFileSync(
|
|
130
|
-
resolve(__dirname, "..", "
|
|
130
|
+
resolve(__dirname, "..", "main.ts"),
|
|
131
131
|
"utf-8",
|
|
132
132
|
);
|
|
133
|
-
// Bun.serve
|
|
133
|
+
// Bun.serve is used for the health server in managed mode
|
|
134
134
|
const bunServeMatches = src.match(/Bun\.serve\(/g);
|
|
135
135
|
expect(bunServeMatches).not.toBeNull();
|
|
136
136
|
// There should be exactly 1 Bun.serve call — for health probes only
|
|
137
137
|
expect(bunServeMatches!.length).toBe(1);
|
|
138
138
|
});
|
|
139
139
|
|
|
140
|
-
test("
|
|
140
|
+
test("main.ts uses createNetServer for Unix socket transport", () => {
|
|
141
141
|
const src = readFileSync(
|
|
142
|
-
resolve(__dirname, "..", "
|
|
142
|
+
resolve(__dirname, "..", "main.ts"),
|
|
143
143
|
"utf-8",
|
|
144
144
|
);
|
|
145
|
-
// Uses node:net createServer for the
|
|
145
|
+
// Uses node:net createServer for the socket transport (both modes)
|
|
146
146
|
expect(src).toMatch(/createNetServer/);
|
|
147
147
|
// The net server never listens on a TCP port (only a socket path)
|
|
148
148
|
expect(src).not.toMatch(/netServer\.listen\(\d+/);
|
|
149
149
|
});
|
|
150
|
-
|
|
151
|
-
test("managed-main.ts unlinks socket after accepting one connection", () => {
|
|
152
|
-
const src = readFileSync(
|
|
153
|
-
resolve(__dirname, "..", "managed-main.ts"),
|
|
154
|
-
"utf-8",
|
|
155
|
-
);
|
|
156
|
-
// Should unlink the socket path after connection
|
|
157
|
-
expect(src).toMatch(/unlinkSync\(socketPath\)/);
|
|
158
|
-
// Should close the net server after one connection
|
|
159
|
-
expect(src).toMatch(/netServer\.close\(\)/);
|
|
160
|
-
});
|
|
161
150
|
});
|
|
162
151
|
|
|
163
152
|
// ---------------------------------------------------------------------------
|
|
@@ -165,9 +154,9 @@ describe("managed entrypoint transport isolation", () => {
|
|
|
165
154
|
// ---------------------------------------------------------------------------
|
|
166
155
|
|
|
167
156
|
describe("health probes", () => {
|
|
168
|
-
test("
|
|
157
|
+
test("main.ts serves /healthz and /readyz in managed mode", () => {
|
|
169
158
|
const src = readFileSync(
|
|
170
|
-
resolve(__dirname, "..", "
|
|
159
|
+
resolve(__dirname, "..", "main.ts"),
|
|
171
160
|
"utf-8",
|
|
172
161
|
);
|
|
173
162
|
expect(src).toMatch(/\/healthz/);
|
|
@@ -203,18 +192,25 @@ describe("health probes", () => {
|
|
|
203
192
|
});
|
|
204
193
|
|
|
205
194
|
// ---------------------------------------------------------------------------
|
|
206
|
-
// 3. Local
|
|
195
|
+
// 3. Local socket transport not inherited by subprocesses
|
|
207
196
|
// ---------------------------------------------------------------------------
|
|
208
197
|
|
|
209
198
|
describe("local entrypoint transport isolation", () => {
|
|
210
|
-
test("main.ts serves over
|
|
199
|
+
test("main.ts serves over a Unix socket, never stdio or a TCP listener", () => {
|
|
211
200
|
const src = readFileSync(resolve(__dirname, "..", "main.ts"), "utf-8");
|
|
212
|
-
//
|
|
213
|
-
expect(src).toMatch(/process\.stdin/);
|
|
214
|
-
expect(src).toMatch(/process\.stdout/);
|
|
215
|
-
//
|
|
216
|
-
// never
|
|
217
|
-
|
|
201
|
+
// Socket transport only — no stdio-child transport.
|
|
202
|
+
expect(src).not.toMatch(/process\.stdin/);
|
|
203
|
+
expect(src).not.toMatch(/process\.stdout/);
|
|
204
|
+
// Bun.serve is only used inside the managed-mode health server block
|
|
205
|
+
// (startHealthServer), never for RPC transport. The RPC transport uses
|
|
206
|
+
// node:net createServer listening on a Unix socket path.
|
|
207
|
+
const bunServeMatches = src.match(/Bun\.serve\(/g);
|
|
208
|
+
expect(bunServeMatches).not.toBeNull();
|
|
209
|
+
expect(bunServeMatches!.length).toBe(1);
|
|
210
|
+
// The Bun.serve call must be inside the startHealthServer function, which
|
|
211
|
+
// is only invoked in managed mode.
|
|
212
|
+
expect(src).toMatch(/function startHealthServer/);
|
|
213
|
+
// Never listens on a numeric TCP port for RPC transport.
|
|
218
214
|
expect(src).not.toMatch(/\.listen\(\d+/);
|
|
219
215
|
});
|
|
220
216
|
|
package/src/cli.ts
CHANGED
|
@@ -25,7 +25,7 @@ import { homedir } from "node:os";
|
|
|
25
25
|
import { createLocalSecureKeyBackend } from "./materializers/local-secure-key-backend.js";
|
|
26
26
|
|
|
27
27
|
// ---------------------------------------------------------------------------
|
|
28
|
-
// Path resolution (mirrors managed-main.ts)
|
|
28
|
+
// Path resolution (mirrors the managed-mode path in main.ts)
|
|
29
29
|
// ---------------------------------------------------------------------------
|
|
30
30
|
|
|
31
31
|
function resolveVellumRoot(): string {
|
package/src/index.ts
CHANGED
|
@@ -2,108 +2,28 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @vellumai/credential-executor
|
|
4
4
|
*
|
|
5
|
-
* Credential Execution Service (CES) — an isolated runtime that
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* the egress proxy, and returns sanitised results.
|
|
5
|
+
* Credential Execution Service (CES) — an isolated runtime that stores and
|
|
6
|
+
* serves credentials on behalf of the assistant daemon over RPC. The CES owns
|
|
7
|
+
* the credential store and exposes credential CRUD operations (get, set,
|
|
8
|
+
* delete, list, bulk-set) plus the managed-credential update handshake.
|
|
10
9
|
*
|
|
11
|
-
* This module re-exports the public API surface. For
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* This module re-exports the public API surface. For the entrypoint see
|
|
11
|
+
* `main.ts` — unified for both local (bare-metal sibling) and managed
|
|
12
|
+
* (Kubernetes sidecar) modes, selected by `getCesMode()`.
|
|
14
13
|
*/
|
|
15
14
|
|
|
16
|
-
export {
|
|
17
|
-
CesRpcServer,
|
|
18
|
-
createCesServer,
|
|
19
|
-
createRunAuthenticatedCommandHandler,
|
|
20
|
-
registerCommandExecutionHandler,
|
|
21
|
-
createMakeAuthenticatedRequestHandler,
|
|
22
|
-
createManageSecureCommandToolHandler,
|
|
23
|
-
registerManageSecureCommandToolHandler,
|
|
24
|
-
buildHandlersWithHttp,
|
|
25
|
-
} from "./server.js";
|
|
15
|
+
export { CesRpcServer, createCesServer } from "./server.js";
|
|
26
16
|
export type {
|
|
27
17
|
CesServerOptions,
|
|
28
|
-
ManageSecureCommandToolHandlerDeps,
|
|
29
18
|
RpcHandlerRegistry,
|
|
30
19
|
RpcMethodHandler,
|
|
31
|
-
RunAuthenticatedCommandHandlerOptions,
|
|
32
20
|
SessionContext,
|
|
33
21
|
} from "./server.js";
|
|
34
22
|
|
|
35
23
|
export {
|
|
36
24
|
getCesDataRoot,
|
|
37
|
-
getCesGrantsDir,
|
|
38
|
-
getCesAuditDir,
|
|
39
|
-
getCesToolStoreDir,
|
|
40
25
|
getCesMode,
|
|
41
26
|
getBootstrapSocketPath,
|
|
42
27
|
getHealthPort,
|
|
43
28
|
} from "./paths.js";
|
|
44
29
|
export type { CesMode } from "./paths.js";
|
|
45
|
-
|
|
46
|
-
export { PersistentGrantStore, TemporaryGrantStore } from "./grants/index.js";
|
|
47
|
-
export type {
|
|
48
|
-
PersistentGrant,
|
|
49
|
-
TemporaryGrant,
|
|
50
|
-
TemporaryGrantKind,
|
|
51
|
-
} from "./grants/index.js";
|
|
52
|
-
|
|
53
|
-
export { computeDigest, verifyDigest } from "./toolstore/integrity.js";
|
|
54
|
-
export type { DigestVerificationResult } from "./toolstore/integrity.js";
|
|
55
|
-
|
|
56
|
-
export {
|
|
57
|
-
isValidSha256Hex,
|
|
58
|
-
validateSourceUrl,
|
|
59
|
-
isWorkspaceOriginPath,
|
|
60
|
-
} from "./toolstore/manifest.js";
|
|
61
|
-
export type {
|
|
62
|
-
BundleOrigin,
|
|
63
|
-
ToolstoreManifest,
|
|
64
|
-
} from "./toolstore/manifest.js";
|
|
65
|
-
|
|
66
|
-
export {
|
|
67
|
-
publishBundle,
|
|
68
|
-
readPublishedManifest,
|
|
69
|
-
isBundlePublished,
|
|
70
|
-
getBundleDir,
|
|
71
|
-
getBundleManifestPath,
|
|
72
|
-
getBundleContentPath,
|
|
73
|
-
} from "./toolstore/publish.js";
|
|
74
|
-
export type {
|
|
75
|
-
PublishRequest,
|
|
76
|
-
PublishResult,
|
|
77
|
-
} from "./toolstore/publish.js";
|
|
78
|
-
|
|
79
|
-
export { resolveLocalSubject } from "./subjects/local.js";
|
|
80
|
-
export type {
|
|
81
|
-
ResolvedStaticSubject,
|
|
82
|
-
ResolvedOAuthSubject,
|
|
83
|
-
ResolvedLocalSubject,
|
|
84
|
-
SubjectResolutionResult,
|
|
85
|
-
OAuthConnectionLookup,
|
|
86
|
-
LocalSubjectResolverDeps,
|
|
87
|
-
} from "./subjects/local.js";
|
|
88
|
-
|
|
89
|
-
export { LocalMaterialiser } from "./materializers/local.js";
|
|
90
|
-
export type {
|
|
91
|
-
MaterialisedCredential,
|
|
92
|
-
MaterialisationResult,
|
|
93
|
-
TokenRefreshFn,
|
|
94
|
-
LocalMaterialiserDeps,
|
|
95
|
-
} from "./materializers/local.js";
|
|
96
|
-
|
|
97
|
-
export { executeAuthenticatedCommand } from "./commands/executor.js";
|
|
98
|
-
export type {
|
|
99
|
-
ExecuteCommandRequest,
|
|
100
|
-
ExecuteCommandResult,
|
|
101
|
-
CommandExecutorDeps,
|
|
102
|
-
MaterializeCredentialFn,
|
|
103
|
-
MaterializeCredentialResult,
|
|
104
|
-
} from "./commands/executor.js";
|
|
105
|
-
|
|
106
|
-
export { executeAuthenticatedHttpRequest } from "./http/executor.js";
|
|
107
|
-
export type { HttpExecutorDeps } from "./http/executor.js";
|
|
108
|
-
|
|
109
|
-
export { MANAGED_LOCAL_STATIC_REJECTION_ERROR } from "./managed-errors.js";
|