@vellumai/credential-executor 0.5.7 → 0.5.9
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/node_modules/@vellumai/ces-contracts/src/error.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/grants.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +1 -1
- package/package.json +1 -1
- package/src/__tests__/managed-integration.test.ts +313 -0
- package/src/managed-main.ts +36 -11
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* module so that both sides can depend on it without circular references.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { z } from "zod
|
|
10
|
+
import { z } from "zod";
|
|
11
11
|
import { RpcErrorSchema } from "./error.js";
|
|
12
12
|
|
|
13
13
|
// ---------------------------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -29,6 +29,10 @@ import {
|
|
|
29
29
|
CesRpcMethod,
|
|
30
30
|
type HandshakeAck,
|
|
31
31
|
type ListGrantsResponse,
|
|
32
|
+
type GetCredentialResponse,
|
|
33
|
+
type SetCredentialResponse,
|
|
34
|
+
type DeleteCredentialResponse,
|
|
35
|
+
type ListCredentialsResponse,
|
|
32
36
|
type RpcEnvelope,
|
|
33
37
|
} from "@vellumai/ces-contracts";
|
|
34
38
|
|
|
@@ -40,6 +44,7 @@ import {
|
|
|
40
44
|
createListAuditRecordsHandler,
|
|
41
45
|
} from "../grants/rpc-handlers.js";
|
|
42
46
|
import { CesRpcServer, type RpcHandlerRegistry, type SessionIdRef } from "../server.js";
|
|
47
|
+
import { createLocalSecureKeyBackend } from "../materializers/local-secure-key-backend.js";
|
|
43
48
|
|
|
44
49
|
// ---------------------------------------------------------------------------
|
|
45
50
|
// Environment setup
|
|
@@ -52,6 +57,7 @@ const SAVED_ENV_KEYS = [
|
|
|
52
57
|
"CES_BOOTSTRAP_SOCKET",
|
|
53
58
|
"CES_HEALTH_PORT",
|
|
54
59
|
"CES_MODE",
|
|
60
|
+
"CREDENTIAL_SECURITY_DIR",
|
|
55
61
|
] as const;
|
|
56
62
|
|
|
57
63
|
type SavedEnv = Record<string, string | undefined>;
|
|
@@ -107,6 +113,39 @@ function buildMinimalHandlers(dataDir: string): RpcHandlerRegistry {
|
|
|
107
113
|
return handlers;
|
|
108
114
|
}
|
|
109
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Build an RPC handler registry with credential CRUD handlers backed by
|
|
118
|
+
* a real SecureKeyBackend using a temp directory for credential storage.
|
|
119
|
+
*
|
|
120
|
+
* Mirrors the handler registration in managed-main.ts.
|
|
121
|
+
*/
|
|
122
|
+
function buildCredentialHandlers(vellumRoot: string): RpcHandlerRegistry {
|
|
123
|
+
const secureKeyBackend = createLocalSecureKeyBackend(vellumRoot);
|
|
124
|
+
const handlers: RpcHandlerRegistry = {};
|
|
125
|
+
|
|
126
|
+
handlers[CesRpcMethod.GetCredential] = (async (req: { account: string }) => {
|
|
127
|
+
const value = await secureKeyBackend.get(req.account);
|
|
128
|
+
return { found: value !== undefined, value };
|
|
129
|
+
}) as typeof handlers[string];
|
|
130
|
+
|
|
131
|
+
handlers[CesRpcMethod.SetCredential] = (async (req: { account: string; value: string }) => {
|
|
132
|
+
const ok = await secureKeyBackend.set(req.account, req.value);
|
|
133
|
+
return { ok };
|
|
134
|
+
}) as typeof handlers[string];
|
|
135
|
+
|
|
136
|
+
handlers[CesRpcMethod.DeleteCredential] = (async (req: { account: string }) => {
|
|
137
|
+
const result = await secureKeyBackend.delete(req.account);
|
|
138
|
+
return { result };
|
|
139
|
+
}) as typeof handlers[string];
|
|
140
|
+
|
|
141
|
+
handlers[CesRpcMethod.ListCredentials] = (async () => {
|
|
142
|
+
const accounts = await secureKeyBackend.list();
|
|
143
|
+
return { accounts };
|
|
144
|
+
}) as typeof handlers[string];
|
|
145
|
+
|
|
146
|
+
return handlers;
|
|
147
|
+
}
|
|
148
|
+
|
|
110
149
|
/**
|
|
111
150
|
* Accept a single connection on a Unix socket and return
|
|
112
151
|
* readable/writable streams plus cleanup helpers.
|
|
@@ -629,3 +668,277 @@ describe("managed CES integration (real Unix socket)", () => {
|
|
|
629
668
|
controller.abort();
|
|
630
669
|
});
|
|
631
670
|
});
|
|
671
|
+
|
|
672
|
+
// ---------------------------------------------------------------------------
|
|
673
|
+
// Credential CRUD RPC tests
|
|
674
|
+
// ---------------------------------------------------------------------------
|
|
675
|
+
|
|
676
|
+
describe("credential CRUD RPC", () => {
|
|
677
|
+
/**
|
|
678
|
+
* Helper: set up a Unix socket server with credential CRUD handlers,
|
|
679
|
+
* connect a client, and complete the handshake. Returns the client
|
|
680
|
+
* socket and a serve promise for cleanup.
|
|
681
|
+
*/
|
|
682
|
+
async function setupCredentialRpc(): Promise<{
|
|
683
|
+
clientSock: Socket;
|
|
684
|
+
servePromise: Promise<void>;
|
|
685
|
+
}> {
|
|
686
|
+
savedEnv = saveEnv();
|
|
687
|
+
tmpDir = mkdtempSync(join(tmpdir(), "ces-cred-integ-"));
|
|
688
|
+
const dataDir = join(tmpDir, "ces-data");
|
|
689
|
+
const securityDir = join(tmpDir, "security");
|
|
690
|
+
const socketDir = join(tmpDir, "bootstrap");
|
|
691
|
+
const socketPath = join(socketDir, "ces.sock");
|
|
692
|
+
mkdirSync(dataDir, { recursive: true });
|
|
693
|
+
mkdirSync(securityDir, { recursive: true });
|
|
694
|
+
mkdirSync(socketDir, { recursive: true });
|
|
695
|
+
|
|
696
|
+
process.env["CES_DATA_DIR"] = dataDir;
|
|
697
|
+
process.env["CES_MODE"] = "managed";
|
|
698
|
+
process.env["CREDENTIAL_SECURITY_DIR"] = securityDir;
|
|
699
|
+
|
|
700
|
+
controller = new AbortController();
|
|
701
|
+
|
|
702
|
+
const connectionPromise = acceptOneConnection(socketPath, controller.signal);
|
|
703
|
+
clientSocket = await connectToSocket(socketPath);
|
|
704
|
+
const conn = await connectionPromise;
|
|
705
|
+
|
|
706
|
+
// vellumRoot is unused when CREDENTIAL_SECURITY_DIR is set,
|
|
707
|
+
// but we pass dataDir for consistency with the backend API.
|
|
708
|
+
const handlers = buildCredentialHandlers(dataDir);
|
|
709
|
+
|
|
710
|
+
serverRpcServer = new CesRpcServer({
|
|
711
|
+
input: conn.readable,
|
|
712
|
+
output: conn.writable,
|
|
713
|
+
handlers,
|
|
714
|
+
logger: { log: () => {}, warn: () => {}, error: () => {} },
|
|
715
|
+
signal: controller.signal,
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
const servePromise = serverRpcServer.serve();
|
|
719
|
+
|
|
720
|
+
// Complete the handshake
|
|
721
|
+
sendMessage(clientSocket, {
|
|
722
|
+
type: "handshake_request",
|
|
723
|
+
protocolVersion: CES_PROTOCOL_VERSION,
|
|
724
|
+
sessionId: `cred-integ-${Date.now()}`,
|
|
725
|
+
});
|
|
726
|
+
const hsMessages = await readMessages(clientSocket, 1);
|
|
727
|
+
const ack = hsMessages[0] as HandshakeAck;
|
|
728
|
+
expect(ack.type).toBe("handshake_ack");
|
|
729
|
+
expect(ack.accepted).toBe(true);
|
|
730
|
+
|
|
731
|
+
return { clientSock: clientSocket, servePromise };
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
test("set + get round-trip", async () => {
|
|
735
|
+
const { clientSock, servePromise } = await setupCredentialRpc();
|
|
736
|
+
|
|
737
|
+
// Set credential
|
|
738
|
+
sendMessage(clientSock, {
|
|
739
|
+
type: "rpc",
|
|
740
|
+
id: "cred-set-1",
|
|
741
|
+
kind: "request",
|
|
742
|
+
method: CesRpcMethod.SetCredential,
|
|
743
|
+
payload: { account: "test-key", value: "secret-value" },
|
|
744
|
+
timestamp: new Date().toISOString(),
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
const setMessages = await readMessages(clientSock, 1);
|
|
748
|
+
expect(setMessages.length).toBe(1);
|
|
749
|
+
const setResp = setMessages[0] as RpcEnvelope & { type: "rpc" };
|
|
750
|
+
expect(setResp.id).toBe("cred-set-1");
|
|
751
|
+
expect(setResp.kind).toBe("response");
|
|
752
|
+
expect(setResp.method).toBe(CesRpcMethod.SetCredential);
|
|
753
|
+
const setPayload = setResp.payload as SetCredentialResponse;
|
|
754
|
+
expect(setPayload.ok).toBe(true);
|
|
755
|
+
|
|
756
|
+
// Get credential
|
|
757
|
+
sendMessage(clientSock, {
|
|
758
|
+
type: "rpc",
|
|
759
|
+
id: "cred-get-1",
|
|
760
|
+
kind: "request",
|
|
761
|
+
method: CesRpcMethod.GetCredential,
|
|
762
|
+
payload: { account: "test-key" },
|
|
763
|
+
timestamp: new Date().toISOString(),
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
const getMessages = await readMessages(clientSock, 1);
|
|
767
|
+
expect(getMessages.length).toBe(1);
|
|
768
|
+
const getResp = getMessages[0] as RpcEnvelope & { type: "rpc" };
|
|
769
|
+
expect(getResp.id).toBe("cred-get-1");
|
|
770
|
+
expect(getResp.kind).toBe("response");
|
|
771
|
+
expect(getResp.method).toBe(CesRpcMethod.GetCredential);
|
|
772
|
+
const getPayload = getResp.payload as GetCredentialResponse;
|
|
773
|
+
expect(getPayload).toEqual({ found: true, value: "secret-value" });
|
|
774
|
+
|
|
775
|
+
clientSock.end();
|
|
776
|
+
controller.abort();
|
|
777
|
+
await servePromise;
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
test("list includes set key", async () => {
|
|
781
|
+
const { clientSock, servePromise } = await setupCredentialRpc();
|
|
782
|
+
|
|
783
|
+
// Set a credential first
|
|
784
|
+
sendMessage(clientSock, {
|
|
785
|
+
type: "rpc",
|
|
786
|
+
id: "cred-set-2",
|
|
787
|
+
kind: "request",
|
|
788
|
+
method: CesRpcMethod.SetCredential,
|
|
789
|
+
payload: { account: "test-key", value: "secret-value" },
|
|
790
|
+
timestamp: new Date().toISOString(),
|
|
791
|
+
});
|
|
792
|
+
await readMessages(clientSock, 1);
|
|
793
|
+
|
|
794
|
+
// List credentials
|
|
795
|
+
sendMessage(clientSock, {
|
|
796
|
+
type: "rpc",
|
|
797
|
+
id: "cred-list-1",
|
|
798
|
+
kind: "request",
|
|
799
|
+
method: CesRpcMethod.ListCredentials,
|
|
800
|
+
payload: {},
|
|
801
|
+
timestamp: new Date().toISOString(),
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
const listMessages = await readMessages(clientSock, 1);
|
|
805
|
+
expect(listMessages.length).toBe(1);
|
|
806
|
+
const listResp = listMessages[0] as RpcEnvelope & { type: "rpc" };
|
|
807
|
+
expect(listResp.id).toBe("cred-list-1");
|
|
808
|
+
expect(listResp.kind).toBe("response");
|
|
809
|
+
expect(listResp.method).toBe(CesRpcMethod.ListCredentials);
|
|
810
|
+
const listPayload = listResp.payload as ListCredentialsResponse;
|
|
811
|
+
expect(listPayload.accounts).toContain("test-key");
|
|
812
|
+
|
|
813
|
+
clientSock.end();
|
|
814
|
+
controller.abort();
|
|
815
|
+
await servePromise;
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
test("delete credential", async () => {
|
|
819
|
+
const { clientSock, servePromise } = await setupCredentialRpc();
|
|
820
|
+
|
|
821
|
+
// Set a credential first
|
|
822
|
+
sendMessage(clientSock, {
|
|
823
|
+
type: "rpc",
|
|
824
|
+
id: "cred-set-3",
|
|
825
|
+
kind: "request",
|
|
826
|
+
method: CesRpcMethod.SetCredential,
|
|
827
|
+
payload: { account: "test-key", value: "secret-value" },
|
|
828
|
+
timestamp: new Date().toISOString(),
|
|
829
|
+
});
|
|
830
|
+
await readMessages(clientSock, 1);
|
|
831
|
+
|
|
832
|
+
// Delete credential
|
|
833
|
+
sendMessage(clientSock, {
|
|
834
|
+
type: "rpc",
|
|
835
|
+
id: "cred-del-1",
|
|
836
|
+
kind: "request",
|
|
837
|
+
method: CesRpcMethod.DeleteCredential,
|
|
838
|
+
payload: { account: "test-key" },
|
|
839
|
+
timestamp: new Date().toISOString(),
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
const delMessages = await readMessages(clientSock, 1);
|
|
843
|
+
expect(delMessages.length).toBe(1);
|
|
844
|
+
const delResp = delMessages[0] as RpcEnvelope & { type: "rpc" };
|
|
845
|
+
expect(delResp.id).toBe("cred-del-1");
|
|
846
|
+
expect(delResp.kind).toBe("response");
|
|
847
|
+
expect(delResp.method).toBe(CesRpcMethod.DeleteCredential);
|
|
848
|
+
const delPayload = delResp.payload as DeleteCredentialResponse;
|
|
849
|
+
expect(delPayload).toEqual({ result: "deleted" });
|
|
850
|
+
|
|
851
|
+
clientSock.end();
|
|
852
|
+
controller.abort();
|
|
853
|
+
await servePromise;
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
test("get after delete returns not found", async () => {
|
|
857
|
+
const { clientSock, servePromise } = await setupCredentialRpc();
|
|
858
|
+
|
|
859
|
+
// Set a credential
|
|
860
|
+
sendMessage(clientSock, {
|
|
861
|
+
type: "rpc",
|
|
862
|
+
id: "cred-set-4",
|
|
863
|
+
kind: "request",
|
|
864
|
+
method: CesRpcMethod.SetCredential,
|
|
865
|
+
payload: { account: "test-key", value: "secret-value" },
|
|
866
|
+
timestamp: new Date().toISOString(),
|
|
867
|
+
});
|
|
868
|
+
await readMessages(clientSock, 1);
|
|
869
|
+
|
|
870
|
+
// Delete it
|
|
871
|
+
sendMessage(clientSock, {
|
|
872
|
+
type: "rpc",
|
|
873
|
+
id: "cred-del-2",
|
|
874
|
+
kind: "request",
|
|
875
|
+
method: CesRpcMethod.DeleteCredential,
|
|
876
|
+
payload: { account: "test-key" },
|
|
877
|
+
timestamp: new Date().toISOString(),
|
|
878
|
+
});
|
|
879
|
+
await readMessages(clientSock, 1);
|
|
880
|
+
|
|
881
|
+
// Get after delete
|
|
882
|
+
sendMessage(clientSock, {
|
|
883
|
+
type: "rpc",
|
|
884
|
+
id: "cred-get-2",
|
|
885
|
+
kind: "request",
|
|
886
|
+
method: CesRpcMethod.GetCredential,
|
|
887
|
+
payload: { account: "test-key" },
|
|
888
|
+
timestamp: new Date().toISOString(),
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
const getMessages = await readMessages(clientSock, 1);
|
|
892
|
+
expect(getMessages.length).toBe(1);
|
|
893
|
+
const getResp = getMessages[0] as RpcEnvelope & { type: "rpc" };
|
|
894
|
+
expect(getResp.id).toBe("cred-get-2");
|
|
895
|
+
expect(getResp.kind).toBe("response");
|
|
896
|
+
expect(getResp.method).toBe(CesRpcMethod.GetCredential);
|
|
897
|
+
const getPayload = getResp.payload as GetCredentialResponse;
|
|
898
|
+
expect(getPayload).toEqual({ found: false });
|
|
899
|
+
|
|
900
|
+
clientSock.end();
|
|
901
|
+
controller.abort();
|
|
902
|
+
await servePromise;
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
test("delete non-existent credential returns not-found", async () => {
|
|
906
|
+
const { clientSock, servePromise } = await setupCredentialRpc();
|
|
907
|
+
|
|
908
|
+
// Set a credential first to initialize the store file on disk.
|
|
909
|
+
// Without a store file, delete returns "error" (no store) rather
|
|
910
|
+
// than "not-found" (store exists, key absent).
|
|
911
|
+
sendMessage(clientSock, {
|
|
912
|
+
type: "rpc",
|
|
913
|
+
id: "cred-set-init",
|
|
914
|
+
kind: "request",
|
|
915
|
+
method: CesRpcMethod.SetCredential,
|
|
916
|
+
payload: { account: "init-key", value: "init-value" },
|
|
917
|
+
timestamp: new Date().toISOString(),
|
|
918
|
+
});
|
|
919
|
+
await readMessages(clientSock, 1);
|
|
920
|
+
|
|
921
|
+
// Delete a credential that was never set
|
|
922
|
+
sendMessage(clientSock, {
|
|
923
|
+
type: "rpc",
|
|
924
|
+
id: "cred-del-3",
|
|
925
|
+
kind: "request",
|
|
926
|
+
method: CesRpcMethod.DeleteCredential,
|
|
927
|
+
payload: { account: "nonexistent" },
|
|
928
|
+
timestamp: new Date().toISOString(),
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
const delMessages = await readMessages(clientSock, 1);
|
|
932
|
+
expect(delMessages.length).toBe(1);
|
|
933
|
+
const delResp = delMessages[0] as RpcEnvelope & { type: "rpc" };
|
|
934
|
+
expect(delResp.id).toBe("cred-del-3");
|
|
935
|
+
expect(delResp.kind).toBe("response");
|
|
936
|
+
expect(delResp.method).toBe(CesRpcMethod.DeleteCredential);
|
|
937
|
+
const delPayload = delResp.payload as DeleteCredentialResponse;
|
|
938
|
+
expect(delPayload).toEqual({ result: "not-found" });
|
|
939
|
+
|
|
940
|
+
clientSock.end();
|
|
941
|
+
controller.abort();
|
|
942
|
+
await servePromise;
|
|
943
|
+
});
|
|
944
|
+
});
|
package/src/managed-main.ts
CHANGED
|
@@ -57,6 +57,7 @@ import { materializeManagedToken } from "./materializers/managed-platform.js";
|
|
|
57
57
|
import { HandleType, parseHandle } from "@vellumai/ces-contracts";
|
|
58
58
|
import { buildLazyGetters, type ApiKeyRef } from "./managed-lazy-getters.js";
|
|
59
59
|
import { MANAGED_LOCAL_STATIC_REJECTION_ERROR } from "./managed-errors.js";
|
|
60
|
+
import type { SecureKeyBackend } from "@vellumai/credential-storage";
|
|
60
61
|
import { createLocalSecureKeyBackend } from "./materializers/local-secure-key-backend.js";
|
|
61
62
|
import { handleCredentialRoute, type CredentialRouteDeps } from "./http/credential-routes.js";
|
|
62
63
|
|
|
@@ -90,7 +91,7 @@ function ensureDataDirs(): void {
|
|
|
90
91
|
// Build RPC handler registry (managed mode)
|
|
91
92
|
// ---------------------------------------------------------------------------
|
|
92
93
|
|
|
93
|
-
function buildHandlers(sessionIdRef: SessionIdRef, apiKeyRef: ApiKeyRef): RpcHandlerRegistry {
|
|
94
|
+
function buildHandlers(sessionIdRef: SessionIdRef, apiKeyRef: ApiKeyRef, secureKeyBackend: SecureKeyBackend): RpcHandlerRegistry {
|
|
94
95
|
// -- Grant stores ----------------------------------------------------------
|
|
95
96
|
const persistentGrantStore = new PersistentGrantStore(
|
|
96
97
|
getCesGrantsDir("managed"),
|
|
@@ -129,10 +130,9 @@ function buildHandlers(sessionIdRef: SessionIdRef, apiKeyRef: ApiKeyRef): RpcHan
|
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
// -- Workspace root for command execution cwd ------------------------------
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
const defaultWorkspaceDir = process.env["WORKSPACE_DIR"] ?? (() => {
|
|
133
|
+
// Use VELLUM_WORKSPACE_DIR when set, otherwise fall back to the legacy
|
|
134
|
+
// path derived from the assistant data mount.
|
|
135
|
+
const defaultWorkspaceDir = process.env["VELLUM_WORKSPACE_DIR"] ?? (() => {
|
|
136
136
|
const assistantDataMount =
|
|
137
137
|
process.env["CES_ASSISTANT_DATA_MOUNT"] ?? "/assistant-data-ro";
|
|
138
138
|
return join(join(assistantDataMount, ".vellum"), "workspace");
|
|
@@ -322,6 +322,27 @@ function buildHandlers(sessionIdRef: SessionIdRef, apiKeyRef: ApiKeyRef): RpcHan
|
|
|
322
322
|
auditStore,
|
|
323
323
|
}) as typeof handlers[string];
|
|
324
324
|
|
|
325
|
+
// Register credential CRUD handlers
|
|
326
|
+
handlers[CesRpcMethod.GetCredential] = (async (req: { account: string }) => {
|
|
327
|
+
const value = await secureKeyBackend.get(req.account);
|
|
328
|
+
return { found: value !== undefined, value };
|
|
329
|
+
}) as typeof handlers[string];
|
|
330
|
+
|
|
331
|
+
handlers[CesRpcMethod.SetCredential] = (async (req: { account: string; value: string }) => {
|
|
332
|
+
const ok = await secureKeyBackend.set(req.account, req.value);
|
|
333
|
+
return { ok };
|
|
334
|
+
}) as typeof handlers[string];
|
|
335
|
+
|
|
336
|
+
handlers[CesRpcMethod.DeleteCredential] = (async (req: { account: string }) => {
|
|
337
|
+
const result = await secureKeyBackend.delete(req.account);
|
|
338
|
+
return { result };
|
|
339
|
+
}) as typeof handlers[string];
|
|
340
|
+
|
|
341
|
+
handlers[CesRpcMethod.ListCredentials] = (async () => {
|
|
342
|
+
const accounts = await secureKeyBackend.list();
|
|
343
|
+
return { accounts };
|
|
344
|
+
}) as typeof handlers[string];
|
|
345
|
+
|
|
325
346
|
return handlers;
|
|
326
347
|
}
|
|
327
348
|
|
|
@@ -498,6 +519,14 @@ async function main(): Promise<void> {
|
|
|
498
519
|
process.on("SIGTERM", shutdown);
|
|
499
520
|
process.on("SIGINT", shutdown);
|
|
500
521
|
|
|
522
|
+
// Create the secure key backend unconditionally — it's needed by both
|
|
523
|
+
// HTTP credential routes (when CES_SERVICE_TOKEN is set) and RPC
|
|
524
|
+
// credential CRUD handlers (always available).
|
|
525
|
+
const assistantDataMount =
|
|
526
|
+
process.env["CES_ASSISTANT_DATA_MOUNT"] ?? "/assistant-data-ro";
|
|
527
|
+
const vellumRoot = join(assistantDataMount, ".vellum");
|
|
528
|
+
const secureKeyBackend = createLocalSecureKeyBackend(vellumRoot);
|
|
529
|
+
|
|
501
530
|
// Set up credential CRUD routes if a service token is configured.
|
|
502
531
|
// The assistant and gateway use CES_SERVICE_TOKEN to authenticate
|
|
503
532
|
// credential management requests over HTTP.
|
|
@@ -505,11 +534,7 @@ async function main(): Promise<void> {
|
|
|
505
534
|
let credentialDeps: CredentialRouteDeps | null = null;
|
|
506
535
|
|
|
507
536
|
if (serviceToken) {
|
|
508
|
-
|
|
509
|
-
process.env["CES_ASSISTANT_DATA_MOUNT"] ?? "/assistant-data-ro";
|
|
510
|
-
const vellumRoot = join(assistantDataMount, ".vellum");
|
|
511
|
-
const backend = createLocalSecureKeyBackend(vellumRoot);
|
|
512
|
-
credentialDeps = { backend, serviceToken };
|
|
537
|
+
credentialDeps = { backend: secureKeyBackend, serviceToken };
|
|
513
538
|
log("Credential CRUD routes enabled (CES_SERVICE_TOKEN configured)");
|
|
514
539
|
} else {
|
|
515
540
|
warn(
|
|
@@ -545,7 +570,7 @@ async function main(): Promise<void> {
|
|
|
545
570
|
// are available to handlers at call time (after the handshake completes).
|
|
546
571
|
const sessionIdRef: SessionIdRef = { current: `ces-managed-${Date.now()}` };
|
|
547
572
|
const apiKeyRef: ApiKeyRef = { current: "" };
|
|
548
|
-
const handlers = buildHandlers(sessionIdRef, apiKeyRef);
|
|
573
|
+
const handlers = buildHandlers(sessionIdRef, apiKeyRef, secureKeyBackend);
|
|
549
574
|
|
|
550
575
|
const server = new CesRpcServer({
|
|
551
576
|
input: connection.readable,
|