@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
@@ -2,13 +2,13 @@
2
2
  * @vellumai/service-contracts/credential-rpc
3
3
  *
4
4
  * Domain entrypoint for the CES (Credential Execution Service) transport and
5
- * RPC surface. Re-exports the transport/RPC/handles/error contracts without
6
- * the trust-rule helpers.
5
+ * RPC surface. Re-exports the transport/RPC/handles/grants/rendering/error
6
+ * contracts without the trust-rule helpers.
7
7
  *
8
8
  * Prefer this subpath over the root `.` import when you only need the
9
9
  * credential RPC surface:
10
10
  *
11
- * import { CesRpcMethod, GetCredentialSchema } from "@vellumai/service-contracts/credential-rpc";
11
+ * import { CesRpcMethod, MakeAuthenticatedRequestSchema } from "@vellumai/service-contracts/credential-rpc";
12
12
  *
13
13
  * For trust-rule types use the dedicated subpath:
14
14
  *
@@ -18,4 +18,6 @@
18
18
  export * from "./transport.js";
19
19
  export * from "./error.js";
20
20
  export * from "./handles.js";
21
+ export * from "./grants.js";
22
+ export * from "./rendering.js";
21
23
  export * from "./rpc.js";
@@ -0,0 +1,184 @@
1
+ /**
2
+ * CES grant proposal, grant record, and audit record schemas.
3
+ *
4
+ * These schemas define the wire format for:
5
+ * - HTTP grant proposals (requesting permission to make an authenticated HTTP call)
6
+ * - Command grant proposals (requesting permission to run an authenticated command)
7
+ * - Temporary grant decisions (approval/denial from a guardian)
8
+ * - Persistent grant records (stored by CES after approval)
9
+ * - Audit record summaries (materialization events)
10
+ */
11
+
12
+ import { z } from "zod";
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Grant proposal types
16
+ // ---------------------------------------------------------------------------
17
+
18
+ /**
19
+ * Proposal to make an authenticated HTTP request using a credential.
20
+ */
21
+ export const HttpGrantProposalSchema = z.object({
22
+ type: z.literal("http"),
23
+ /** CES credential handle identifying which credential to use. */
24
+ credentialHandle: z.string(),
25
+ /** HTTP method (e.g. "GET", "POST"). */
26
+ method: z.string(),
27
+ /** Target URL. */
28
+ url: z.string(),
29
+ /** Human-readable description of why this request is needed. */
30
+ purpose: z.string(),
31
+ /** Optional constrained set of URL patterns this grant covers. */
32
+ allowedUrlPatterns: z.array(z.string()).optional(),
33
+ });
34
+ export type HttpGrantProposal = z.infer<typeof HttpGrantProposalSchema>;
35
+
36
+ /**
37
+ * Proposal to run an authenticated command using credential environment variables.
38
+ */
39
+ export const CommandGrantProposalSchema = z.object({
40
+ type: z.literal("command"),
41
+ /** CES credential handle identifying which credential to use. */
42
+ credentialHandle: z.string(),
43
+ /** The command to execute (without credential values — CES injects those). */
44
+ command: z.string(),
45
+ /** Human-readable description of why this command is needed. */
46
+ purpose: z.string(),
47
+ /** Optional constrained set of command patterns this grant covers. */
48
+ allowedCommandPatterns: z.array(z.string()).optional(),
49
+ });
50
+ export type CommandGrantProposal = z.infer<typeof CommandGrantProposalSchema>;
51
+
52
+ /**
53
+ * Union of all grant proposal types.
54
+ */
55
+ export const GrantProposalSchema = z.discriminatedUnion("type", [
56
+ HttpGrantProposalSchema,
57
+ CommandGrantProposalSchema,
58
+ ]);
59
+ export type GrantProposal = z.infer<typeof GrantProposalSchema>;
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Grant decisions (temporary — before persistence)
63
+ // ---------------------------------------------------------------------------
64
+
65
+ export const GrantDecision = {
66
+ Approved: "approved",
67
+ Denied: "denied",
68
+ } as const;
69
+
70
+ export type GrantDecision = (typeof GrantDecision)[keyof typeof GrantDecision];
71
+
72
+ /**
73
+ * A temporary grant decision from a guardian, before CES persists it.
74
+ */
75
+ export const TemporaryGrantDecisionSchema = z.object({
76
+ /** The proposal this decision applies to. */
77
+ proposal: GrantProposalSchema,
78
+ /** Deterministic hash of the proposal (see rendering.ts). */
79
+ proposalHash: z.string(),
80
+ /** The guardian's decision. */
81
+ decision: z.enum(["approved", "denied"]),
82
+ /** Who made the decision (guardian identifier). */
83
+ decidedBy: z.string(),
84
+ /** ISO-8601 timestamp of the decision. */
85
+ decidedAt: z.string(),
86
+ /** Optional human-readable reason for the decision. */
87
+ reason: z.string().optional(),
88
+ /** How long the grant should remain valid (ISO-8601 duration, e.g. "PT1H"). */
89
+ ttl: z.string().optional(),
90
+ /**
91
+ * The type of grant to create. Determines persistence behaviour:
92
+ * - `allow_once`: Temporary single-use grant (consumed after one use)
93
+ * - `allow_10m`: Temporary timed grant (10-minute TTL)
94
+ * - `allow_conversation`: Temporary conversation-scoped grant (lives for conversation)
95
+ * - `always_allow`: Persistent grant (survives restart)
96
+ *
97
+ * When omitted, defaults to `always_allow` for backwards compatibility.
98
+ */
99
+ grantType: z.enum(["allow_once", "allow_10m", "allow_conversation", "always_allow"]).optional(),
100
+ });
101
+ export type TemporaryGrantDecision = z.infer<
102
+ typeof TemporaryGrantDecisionSchema
103
+ >;
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // Persistent grant records (CES-owned)
107
+ // ---------------------------------------------------------------------------
108
+
109
+ export const GrantStatus = {
110
+ Active: "active",
111
+ Expired: "expired",
112
+ Revoked: "revoked",
113
+ Consumed: "consumed",
114
+ } as const;
115
+
116
+ export type GrantStatus = (typeof GrantStatus)[keyof typeof GrantStatus];
117
+
118
+ /**
119
+ * A persistent grant record stored by CES.
120
+ *
121
+ * Grants authorize a specific agent connection to use a credential for a
122
+ * constrained purpose. They are never sent to the assistant with secret
123
+ * values — only metadata.
124
+ */
125
+ export const PersistentGrantRecordSchema = z.object({
126
+ /** Unique grant identifier. */
127
+ grantId: z.string(),
128
+ /** The CES connection that created this grant. */
129
+ sessionId: z.string(),
130
+ /** The credential handle this grant authorizes. */
131
+ credentialHandle: z.string(),
132
+ /** The proposal type (http or command). */
133
+ proposalType: z.enum(["http", "command"]),
134
+ /** Deterministic hash of the original proposal. */
135
+ proposalHash: z.string(),
136
+ /** Constrained purposes — URL patterns for HTTP, command patterns for commands. */
137
+ allowedPurposes: z.array(z.string()),
138
+ /** Current grant status. */
139
+ status: z.enum(["active", "expired", "revoked", "consumed"]),
140
+ /** Who approved the grant. */
141
+ grantedBy: z.string(),
142
+ /** ISO-8601 timestamp when the grant was created. */
143
+ createdAt: z.string(),
144
+ /** ISO-8601 timestamp when the grant expires (null if no expiry). */
145
+ expiresAt: z.string().nullable(),
146
+ /** ISO-8601 timestamp when the grant was consumed (null if unconsumed). */
147
+ consumedAt: z.string().nullable(),
148
+ /** ISO-8601 timestamp when the grant was revoked (null if active). */
149
+ revokedAt: z.string().nullable(),
150
+ });
151
+ export type PersistentGrantRecord = z.infer<typeof PersistentGrantRecordSchema>;
152
+
153
+ // ---------------------------------------------------------------------------
154
+ // Audit record summaries
155
+ // ---------------------------------------------------------------------------
156
+
157
+ /**
158
+ * Summary of a credential materialization event, as exposed by CES
159
+ * for audit inspection.
160
+ *
161
+ * Audit records never contain secret values — only metadata about what
162
+ * was accessed, when, by whom, and whether it succeeded.
163
+ */
164
+ export const AuditRecordSummarySchema = z.object({
165
+ /** Unique audit record identifier. */
166
+ auditId: z.string(),
167
+ /** The grant that authorized this materialization. */
168
+ grantId: z.string(),
169
+ /** The credential handle that was materialized. */
170
+ credentialHandle: z.string(),
171
+ /** The tool that triggered materialization. */
172
+ toolName: z.string(),
173
+ /** Target of the operation (URL for HTTP, command summary for commands). */
174
+ target: z.string(),
175
+ /** The CES connection that triggered materialization. */
176
+ sessionId: z.string(),
177
+ /** Whether the execution succeeded. */
178
+ success: z.boolean(),
179
+ /** Error message if execution failed (no secrets). */
180
+ errorMessage: z.string().optional(),
181
+ /** ISO-8601 timestamp of the materialization event. */
182
+ timestamp: z.string(),
183
+ });
184
+ export type AuditRecordSummary = z.infer<typeof AuditRecordSummarySchema>;
@@ -4,13 +4,13 @@
4
4
  * This is a compatibility aggregate that re-exports everything from all
5
5
  * submodules. Prefer the explicit domain subpaths for new code:
6
6
  *
7
- * - `@vellumai/service-contracts/credential-rpc` — transport, RPC, handles, error
7
+ * - `@vellumai/service-contracts/credential-rpc` — transport, RPC, handles, grants, rendering, error
8
8
  * - `@vellumai/service-contracts/trust-rules` — trust-rule types and parsing helpers
9
9
  * - `@vellumai/service-contracts/twilio-ingress` — shared Twilio ingress config constants
10
10
  * - `@vellumai/service-contracts/ingress` — shared public ingress URL helpers
11
11
  *
12
12
  * Fine-grained subpaths are also available for low-friction migration:
13
- * `./rpc`, `./handles`, `./error`, `./trust-rules`, `./ingress`, `./twilio-ingress`
13
+ * `./rpc`, `./handles`, `./grants`, `./rendering`, `./error`, `./trust-rules`, `./ingress`, `./twilio-ingress`
14
14
  *
15
15
  * Neutral wire-protocol contracts for communication between the assistant
16
16
  * daemon and the Credential Execution Service (CES). This package is
@@ -23,7 +23,9 @@ export * from "./client-metadata.js";
23
23
  export * from "./transport.js";
24
24
  export * from "./error.js";
25
25
  export * from "./handles.js";
26
+ export * from "./grants.js";
26
27
  export * from "./rpc.js";
28
+ export * from "./rendering.js";
27
29
  export * from "./trust-rules.js";
28
30
  export * from "./ingress.js";
29
31
  export * from "./twilio-ingress.js";
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Canonical proposal rendering and deterministic hashing.
3
+ *
4
+ * Both the assistant and CES must produce identical human-readable text and
5
+ * proposal hashes for the same proposal object. This module provides the
6
+ * shared implementations so neither side has to duplicate the logic.
7
+ *
8
+ * Hashing algorithm:
9
+ * 1. Recursively sort object keys (depth-first).
10
+ * 2. Serialize to a canonical JSON string with no whitespace.
11
+ * 3. SHA-256 hash the UTF-8 bytes and return a lowercase hex digest.
12
+ *
13
+ * This matches the algorithm used by `tool-approval-digest.ts` in the
14
+ * assistant, ensuring consistency across the approval pipeline.
15
+ */
16
+
17
+ import { createHash } from "node:crypto";
18
+ import type { GrantProposal } from "./grants.js";
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Canonical JSON serialization
22
+ // ---------------------------------------------------------------------------
23
+
24
+ /**
25
+ * Recursively sort all object keys and return a deterministic JSON string.
26
+ *
27
+ * Handles nested objects, arrays (element order preserved), and primitive
28
+ * values. `undefined` values inside objects are omitted (matching
29
+ * JSON.stringify semantics). `null` is preserved.
30
+ */
31
+ export function canonicalJsonSerialize(value: unknown): string {
32
+ return JSON.stringify(sortKeysDeep(value));
33
+ }
34
+
35
+ function sortKeysDeep(value: unknown): unknown {
36
+ if (value == null) return value;
37
+
38
+ if (Array.isArray(value)) {
39
+ return value.map(sortKeysDeep);
40
+ }
41
+
42
+ if (typeof value === "object") {
43
+ const sorted: Record<string, unknown> = {};
44
+ const keys = Object.keys(value as Record<string, unknown>).sort();
45
+ for (const key of keys) {
46
+ sorted[key] = sortKeysDeep((value as Record<string, unknown>)[key]);
47
+ }
48
+ return sorted;
49
+ }
50
+
51
+ // Primitive — number, string, boolean
52
+ return value;
53
+ }
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // Proposal hashing
57
+ // ---------------------------------------------------------------------------
58
+
59
+ /**
60
+ * Compute a deterministic SHA-256 hex digest for a grant proposal.
61
+ *
62
+ * Two proposals with the same canonical content (regardless of key ordering)
63
+ * will always produce the same hash. This hash is used to match grant
64
+ * decisions to proposals in the approval pipeline.
65
+ */
66
+ export function hashProposal(proposal: GrantProposal): string {
67
+ const canonical = canonicalJsonSerialize(proposal);
68
+ return createHash("sha256").update(canonical, "utf8").digest("hex");
69
+ }
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // Human-readable proposal rendering
73
+ // ---------------------------------------------------------------------------
74
+
75
+ /**
76
+ * Render a grant proposal as a human-readable text block suitable for
77
+ * display in approval UIs and guardian notifications.
78
+ *
79
+ * The rendering is deterministic for the same proposal, so both assistant
80
+ * and CES produce identical text.
81
+ */
82
+ export function renderProposal(proposal: GrantProposal): string {
83
+ switch (proposal.type) {
84
+ case "http":
85
+ return renderHttpProposal(proposal);
86
+ case "command":
87
+ return renderCommandProposal(proposal);
88
+ default: {
89
+ const _exhaustive: never = proposal;
90
+ throw new Error(`Unknown proposal type: ${(_exhaustive as GrantProposal).type}`);
91
+ }
92
+ }
93
+ }
94
+
95
+ function renderHttpProposal(proposal: GrantProposal & { type: "http" }): string {
96
+ const lines: string[] = [
97
+ `Authenticated HTTP Request`,
98
+ ` Method: ${proposal.method}`,
99
+ ` URL: ${proposal.url}`,
100
+ ` Credential: ${proposal.credentialHandle}`,
101
+ ` Purpose: ${proposal.purpose}`,
102
+ ];
103
+
104
+ if (proposal.allowedUrlPatterns && proposal.allowedUrlPatterns.length > 0) {
105
+ lines.push(` Allowed URL patterns:`);
106
+ for (const pattern of proposal.allowedUrlPatterns) {
107
+ lines.push(` - ${pattern}`);
108
+ }
109
+ }
110
+
111
+ return lines.join("\n");
112
+ }
113
+
114
+ function renderCommandProposal(
115
+ proposal: GrantProposal & { type: "command" },
116
+ ): string {
117
+ const lines: string[] = [
118
+ `Authenticated Command Execution`,
119
+ ` Command: ${proposal.command}`,
120
+ ` Credential: ${proposal.credentialHandle}`,
121
+ ` Purpose: ${proposal.purpose}`,
122
+ ];
123
+
124
+ if (
125
+ proposal.allowedCommandPatterns &&
126
+ proposal.allowedCommandPatterns.length > 0
127
+ ) {
128
+ lines.push(` Allowed command patterns:`);
129
+ for (const pattern of proposal.allowedCommandPatterns) {
130
+ lines.push(` - ${pattern}`);
131
+ }
132
+ }
133
+
134
+ return lines.join("\n");
135
+ }