History

Add executor MCP source OAuth flow

Rhys Sullivan committed 8 months ago

Commit d48e6bc1 parent ea7c60b25 files · +4295 −23Browse files

Unified · Split

25 files
  1. apps/executor/package.json +3 −1
  2. apps/executor/src/cli/main.ts +47 −2
  3. apps/executor/src/server/index.ts +6 −1
  4. apps/executor/src/server/server.test.ts +388 −0
  5. bun.lock +2 −0
  6. packages/control-plane/drizzle/20260306094249_large_giant_girl/migration.sql +39 −0
  7. packages/control-plane/drizzle/20260306094249_large_giant_girl/snapshot.json +2425 −0
  8. packages/control-plane/src/api/local/api.ts +10 −0
  9. packages/control-plane/src/api/local/http.ts +24 −7
  10. packages/control-plane/src/api/service.ts +10 −0
  11. packages/control-plane/src/persistence/control-plane-rows.ts +4 −0
  12. packages/control-plane/src/persistence/repos/index.ts +2 −0
  13. packages/control-plane/src/persistence/repos/secret-materials-repo.ts +55 −0
  14. packages/control-plane/src/persistence/repos/source-auth-sessions-repo.ts +153 −0
  15. packages/control-plane/src/persistence/schema.ts +66 −0
  16. packages/control-plane/src/runtime/execution-service.ts +32 −1
  17. packages/control-plane/src/runtime/executor-tools.ts +112 −0
  18. packages/control-plane/src/runtime/index.ts +15 −1
  19. packages/control-plane/src/runtime/services.ts +28 −2
  20. packages/control-plane/src/runtime/source-auth-service.ts +769 −0
  21. packages/control-plane/src/runtime/workspace-execution-environment.ts +22 −8
  22. packages/control-plane/src/schema/ids.ts +8 −0
  23. packages/control-plane/src/schema/index.ts +2 −0
  24. packages/control-plane/src/schema/models/secret-material.ts +28 −0
  25. packages/control-plane/src/schema/models/source-auth-session.ts +45 −0
apps/executor/package.json+3−1
@@ -25,8 +25,10 @@ · 24 unmodified lines
25 "devDependencies": {25 "devDependencies": {
26 "@executor-v3/mcp-elicitation-demo": "workspace:*",26 "@executor-v3/mcp-elicitation-demo": "workspace:*",
27 "@effect/vitest": "catalog:",27 "@effect/vitest": "catalog:",
28 "@modelcontextprotocol/sdk": "^1.26.0",
28 "@types/node": "catalog:",29 "@types/node": "catalog:",
29 "bun-types": "catalog:",30 "bun-types": "catalog:",
30 "vitest": "catalog:"31 "vitest": "catalog:",
32 "zod": "catalog:"
31 }33 }
32}34}
apps/executor/src/cli/main.ts+47−2
@@ -205,8 +205,7 @@ const promptInteraction = (interaction: ExecutionInteraction) => · 204 unmodified lines
205 yield* Effect.sync(() => {205 yield* Effect.sync(() => {
206 process.stdout.write(`${parsed.message}\n${parsed.url ?? ""}\n`);206 process.stdout.write(`${parsed.message}\n${parsed.url ?? ""}\n`);
207 });207 });
208 yield* promptLine("Press enter when done. ");208 return null;
209 return JSON.stringify({ action: "accept" });
210 }209 }
211 210
212 const line = yield* promptLine(`${parsed.message} [y/N] `);211 const line = yield* promptLine(`${parsed.message} [y/N] `);
@@ -227,6 +226,33 @@ const promptInteraction = (interaction: ExecutionInteraction) => · 14 unmodified lines
227 });226 });
228 });227 });
229 228
229const waitForExecutionProgress = (input: {
230 client: ControlPlaneClient;
231 workspaceId: ExecutionEnvelope["execution"]["workspaceId"];
232 executionId: ExecutionEnvelope["execution"]["id"];
233 pendingInteractionId: ExecutionInteraction["id"];
234}) =>
235 Effect.gen(function* () {
236 while (true) {
237 yield* sleep(SERVER_POLL_INTERVAL_MS);
238
239 const next = yield* input.client.executions.get({
240 path: {
241 workspaceId: input.workspaceId,
242 executionId: input.executionId,
243 },
244 });
245
246 if (
247 next.execution.status !== "waiting_for_interaction"
248 || next.pendingInteraction === null
249 || next.pendingInteraction.id !== input.pendingInteractionId
250 ) {
251 return next;
252 }
253 }
254 });
255
230const printExecution = (envelope: ExecutionEnvelope) =>256const printExecution = (envelope: ExecutionEnvelope) =>
231 Effect.sync(() => {257 Effect.sync(() => {
232 const execution = envelope.execution;258 const execution = envelope.execution;
@@ -314,6 +340,25 @@ const driveExecution = (input: { · 81 unmodified lines
314 return current;340 return current;
315 }341 }
316 342
343 const parsed = parseInteractionPayload(pending);
344 if (parsed?.mode === "url") {
345 yield* Effect.sync(() => {
346 process.stdout.write(`${parsed.message}\n${parsed.url ?? ""}\n`);
347 });
348
349 if (!process.stdin.isTTY || !process.stdout.isTTY) {
350 return current;
351 }
352
353 current = yield* waitForExecutionProgress({
354 client: input.client,
355 workspaceId: input.workspaceId,
356 executionId: current.execution.id,
357 pendingInteractionId: pending.id,
358 });
359 continue;
360 }
361
317 const responseJson = yield* promptInteraction(pending);362 const responseJson = yield* promptInteraction(pending);
318 if (responseJson === null) {363 if (responseJson === null) {
319 yield* Effect.sync(() => {364 yield* Effect.sync(() => {
apps/executor/src/server/index.ts+6−1
@@ -80,11 +80,14 @@ export const makeLocalExecutorServer = ( · 79 unmodified lines
80 });80 });
81 }81 }
82 82
83 let baseUrlRef: string | undefined;
84
83 const runtime = yield* Effect.acquireRelease(85 const runtime = yield* Effect.acquireRelease(
84 makeSqlControlPlaneRuntime({86 makeSqlControlPlaneRuntime({
85 localDataDir,87 localDataDir,
86 executionResolver: options.executionResolver,88 executionResolver: options.executionResolver,
87 resolveSecretMaterial: options.resolveSecretMaterial,89 resolveSecretMaterial: options.resolveSecretMaterial,
90 getLocalServerBaseUrl: () => baseUrlRef,
88 }).pipe(91 }).pipe(
89 Effect.mapError((cause) =>92 Effect.mapError((cause) =>
90 cause instanceof Error ? cause : new Error(String(cause)),93 cause instanceof Error ? cause : new Error(String(cause)),
@@ -110,11 +113,13 @@ export const makeLocalExecutorServer = ( · 19 unmodified lines
110 return yield* Effect.fail(new Error("Local executor server did not bind to a TCP address"));113 return yield* Effect.fail(new Error("Local executor server did not bind to a TCP address"));
111 }114 }
112 115
116 baseUrlRef = `http://${server.address.hostname}:${server.address.port}`;
117
113 return {118 return {
114 runtime,119 runtime,
115 host: server.address.hostname,120 host: server.address.hostname,
116 port: server.address.port,121 port: server.address.port,
117 baseUrl: `http://${server.address.hostname}:${server.address.port}`,122 baseUrl: baseUrlRef,
118 } satisfies LocalExecutorServer;123 } satisfies LocalExecutorServer;
119 });124 });
120 125
apps/executor/src/server/server.test.ts+388−0
@@ -1,4 +1,5 @@
1import { createServer, type IncomingMessage, type ServerResponse } from "node:http";1import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
2import { randomUUID } from "node:crypto";
2 3
3import {4import {
4 HttpApi,5 HttpApi,
@@ -16,9 +17,14 @@ import { makeToolInvokerFromTools } from "@executor-v3/codemode-core"; · 11 unmodified lines
16import {17import {
17 ControlPlaneStorageError,18 ControlPlaneStorageError,
18 makeControlPlaneClient,19 makeControlPlaneClient,
20 type ControlPlaneClient,
19 type ResolveExecutionEnvironment,21 type ResolveExecutionEnvironment,
20} from "@executor-v3/control-plane";22} from "@executor-v3/control-plane";
21import { makeInProcessExecutor } from "@executor-v3/runtime-local-inproc";23import { makeInProcessExecutor } from "@executor-v3/runtime-local-inproc";
24import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
25import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
26import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
27import { z } from "zod/v4";
22 28
23import {29import {
24 seedDemoMcpSourceInWorkspace,30 seedDemoMcpSourceInWorkspace,
@@ -122,6 +128,304 @@ const startOpenApiDemoServer = async () => { · 97 unmodified lines
122 };128 };
123};129};
124 130
131const DEMO_OAUTH_ACCESS_TOKEN = "demo-access-token";
132const DEMO_OAUTH_REFRESH_TOKEN = "demo-refresh-token";
133
134const startOAuthProtectedMcpServer = async () => {
135 const host = "127.0.0.1";
136 const app = createMcpExpressApp({ host });
137 const transports: Record<string, StreamableHTTPServerTransport> = {};
138 const servers: Record<string, McpServer> = {};
139
140 const createAuthorizedServer = () => {
141 const server = new McpServer(
142 {
143 name: "executor-oauth-mcp-demo",
144 version: "1.0.0",
145 },
146 {
147 capabilities: {
148 tools: {},
149 },
150 },
151 );
152
153 server.registerTool(
154 "whoami",
155 {
156 description: "Return the active identity for the OAuth demo source.",
157 inputSchema: {},
158 },
159 async () => ({
160 content: [{ type: "text", text: "oauth-demo" }],
161 }),
162 );
163
164 return server;
165 };
166
167 const listener = await new Promise<import("node:http").Server>((resolve, reject) => {
168 const server = app.listen(0, host);
169
170 const onListening = () => {
171 server.off("error", onError);
172 resolve(server);
173 };
174
175 const onError = (error: Error) => {
176 server.off("listening", onListening);
177 reject(error);
178 };
179
180 server.once("listening", onListening);
181 server.once("error", onError);
182 });
183
184 const address = listener.address();
185 if (!address || typeof address === "string") {
186 throw new Error("Failed to bind OAuth MCP demo server");
187 }
188
189 const baseUrl = `http://${host}:${address.port}`;
190 const endpoint = `${baseUrl}/mcp`;
191 const resourceMetadataUrl = `${baseUrl}/.well-known/oauth-protected-resource/mcp`;
192
193 const requireBearerAuth = (req: IncomingMessage, res: ServerResponse) => {
194 const authorization = req.headers.authorization;
195 if (authorization === `Bearer ${DEMO_OAUTH_ACCESS_TOKEN}`) {
196 return true;
197 }
198
199 res.statusCode = 401;
200 res.setHeader(
201 "WWW-Authenticate",
202 `Bearer resource_metadata="${resourceMetadataUrl}"`,
203 );
204 res.setHeader("content-type", "application/json");
205 res.end(
206 JSON.stringify({
207 error: "unauthorized",
208 }),
209 );
210 return false;
211 };
212
213 app.get("/.well-known/oauth-protected-resource/mcp", (_req: any, res: any) => {
214 res.status(200).json({
215 resource: endpoint,
216 authorization_servers: [baseUrl],
217 scopes_supported: ["openid", "offline_access"],
218 bearer_methods_supported: ["header"],
219 });
220 });
221
222 app.get("/.well-known/oauth-authorization-server", (_req: any, res: any) => {
223 res.status(200).json({
224 issuer: baseUrl,
225 authorization_endpoint: `${baseUrl}/authorize`,
226 token_endpoint: `${baseUrl}/token`,
227 registration_endpoint: `${baseUrl}/register`,
228 response_types_supported: ["code"],
229 grant_types_supported: ["authorization_code", "refresh_token"],
230 code_challenge_methods_supported: ["S256"],
231 token_endpoint_auth_methods_supported: ["none"],
232 });
233 });
234
235 app.post("/register", (req: any, res: any) => {
236 res.status(201).json({
237 ...(req.body ?? {}),
238 client_id: `client_${randomUUID()}`,
239 client_id_issued_at: Math.floor(Date.now() / 1000),
240 });
241 });
242
243 app.get("/authorize", (req: any, res: any) => {
244 const redirectUri = new URL(String(req.query.redirect_uri));
245 redirectUri.searchParams.set("code", "demo-code");
246 if (typeof req.query.state === "string") {
247 redirectUri.searchParams.set("state", req.query.state);
248 }
249
250 res.redirect(302, redirectUri.toString());
251 });
252
253 app.post("/token", (_req: any, res: any) => {
254 res.status(200).json({
255 access_token: DEMO_OAUTH_ACCESS_TOKEN,
256 token_type: "Bearer",
257 expires_in: 3600,
258 refresh_token: DEMO_OAUTH_REFRESH_TOKEN,
259 scope: "openid offline_access",
260 });
261 });
262
263 app.post("/mcp", async (req: any, res: any) => {
264 if (!requireBearerAuth(req, res)) {
265 return;
266 }
267
268 const sessionIdHeader = req.headers["mcp-session-id"];
269 const sessionId =
270 typeof sessionIdHeader === "string"
271 ? sessionIdHeader
272 : Array.isArray(sessionIdHeader)
273 ? sessionIdHeader[0]
274 : undefined;
275
276 try {
277 let transport: StreamableHTTPServerTransport;
278
279 if (sessionId && transports[sessionId]) {
280 transport = transports[sessionId];
281 } else {
282 transport = new StreamableHTTPServerTransport({
283 sessionIdGenerator: () => randomUUID(),
284 onsessioninitialized: (newSessionId) => {
285 transports[newSessionId] = transport;
286 },
287 });
288
289 transport.onclose = () => {
290 const closedSessionId = transport.sessionId;
291 if (closedSessionId && transports[closedSessionId]) {
292 delete transports[closedSessionId];
293 }
294 if (closedSessionId && servers[closedSessionId]) {
295 void servers[closedSessionId].close().catch(() => undefined);
296 delete servers[closedSessionId];
297 }
298 };
299
300 const server = createAuthorizedServer();
301 await server.connect(transport);
302 const newSessionId = transport.sessionId;
303 if (newSessionId) {
304 servers[newSessionId] = server;
305 }
306 }
307
308 await transport.handleRequest(req, res, req.body);
309 } catch (error) {
310 if (!res.headersSent) {
311 res.status(500).json({
312 jsonrpc: "2.0",
313 error: {
314 code: -32603,
315 message: error instanceof Error ? error.message : "Internal server error",
316 },
317 id: null,
318 });
319 }
320 }
321 });
322
323 app.get("/mcp", async (req: any, res: any) => {
324 if (!requireBearerAuth(req, res)) {
325 return;
326 }
327
328 const sessionIdHeader = req.headers["mcp-session-id"];
329 const sessionId =
330 typeof sessionIdHeader === "string"
331 ? sessionIdHeader
332 : Array.isArray(sessionIdHeader)
333 ? sessionIdHeader[0]
334 : undefined;
335
336 if (!sessionId || !transports[sessionId]) {
337 res.status(400).send("Invalid or missing session ID");
338 return;
339 }
340
341 await transports[sessionId].handleRequest(req, res);
342 });
343
344 app.delete("/mcp", async (req: any, res: any) => {
345 if (!requireBearerAuth(req, res)) {
346 return;
347 }
348
349 const sessionIdHeader = req.headers["mcp-session-id"];
350 const sessionId =
351 typeof sessionIdHeader === "string"
352 ? sessionIdHeader
353 : Array.isArray(sessionIdHeader)
354 ? sessionIdHeader[0]
355 : undefined;
356
357 if (!sessionId || !transports[sessionId]) {
358 res.status(400).send("Invalid or missing session ID");
359 return;
360 }
361
362 const transport = transports[sessionId];
363 await transport.handleRequest(req, res, req.body);
364 await transport.close();
365 delete transports[sessionId];
366
367 if (servers[sessionId]) {
368 await servers[sessionId].close().catch(() => undefined);
369 delete servers[sessionId];
370 }
371 });
372
373 return {
374 baseUrl,
375 endpoint,
376 close: async () => {
377 for (const transport of Object.values(transports)) {
378 await transport.close().catch(() => undefined);
379 }
380
381 for (const server of Object.values(servers)) {
382 await server.close().catch(() => undefined);
383 }
384
385 await new Promise<void>((resolve, reject) => {
386 listener.close((error) => (error ? reject(error) : resolve()));
387 });
388 },
389 };
390};
391
392const extractUrlInteractionUrl = (payloadJson: string): string => {
393 const parsed = JSON.parse(payloadJson) as {
394 elicitation?: {
395 url?: string;
396 };
397 };
398
399 const url = parsed.elicitation?.url;
400 if (typeof url !== "string" || url.trim().length === 0) {
401 throw new Error("Missing URL elicitation in payloadJson");
402 }
403
404 return url;
405};
406
407const waitForExecutionCompletion = (input: {
408 client: ControlPlaneClient;
409 workspaceId: string;
410 executionId: string;
411}) =>
412 Effect.gen(function* () {
413 while (true) {
414 const next = yield* input.client.executions.get({
415 path: {
416 workspaceId: input.workspaceId as never,
417 executionId: input.executionId as never,
418 },
419 });
420
421 if (next.execution.status !== "waiting_for_interaction") {
422 return next;
423 }
424
425 yield* Effect.sleep("100 millis");
426 }
427 });
428
125describe("local-executor-server", () => {429describe("local-executor-server", () => {
126 it.scoped("serves the control-plane API and executes code", () =>430 it.scoped("serves the control-plane API and executes code", () =>
127 Effect.gen(function* () {431 Effect.gen(function* () {
@@ -296,6 +600,7 @@ describe("local-executor-server", () => { · 168 unmodified lines
296 expect(resumed.execution.resultJson).toContain(`approved:${value}`);600 expect(resumed.execution.resultJson).toContain(`approved:${value}`);
297 }601 }
298 }),602 }),
603 15_000,
299 );604 );
300 605
301 it.scoped("updates an existing demo MCP source instead of creating a duplicate", () =>606 it.scoped("updates an existing demo MCP source instead of creating a duplicate", () =>
@@ -413,6 +718,89 @@ describe("local-executor-server", () => { · 111 unmodified lines
413 }),718 }),
414 );719 );
415 720
721 it.scoped("adds an OAuth-protected MCP source via executor.sources.add and resumes after callback", () =>
722 Effect.gen(function* () {
723 const oauthServer = yield* Effect.acquireRelease(
724 Effect.promise(() => startOAuthProtectedMcpServer()),
725 (server) => Effect.promise(() => server.close()).pipe(Effect.orDie),
726 );
727
728 const server = yield* makeLocalExecutorServer({
729 port: 0,
730 localDataDir: ":memory:",
731 });
732
733 const bootstrapClient = yield* makeControlPlaneClient({
734 baseUrl: server.baseUrl,
735 });
736 const installation = yield* bootstrapClient.local.installation({});
737 const client = yield* makeControlPlaneClient({
738 baseUrl: server.baseUrl,
739 accountId: installation.accountId,
740 });
741
742 const added = yield* client.executions.create({
743 path: {
744 workspaceId: installation.workspaceId,
745 },
746 payload: {
747 code: `return await tools.executor.sources.add({ endpoint: ${JSON.stringify(oauthServer.endpoint)}, name: "Axiom", namespace: "axiom" });`,
748 },
749 });
750
751 expect(added.execution.status).toBe("waiting_for_interaction");
752 expect(added.pendingInteraction).not.toBeNull();
753 if (added.pendingInteraction === null) {
754 throw new Error("Expected pending OAuth interaction");
755 }
756 expect(added.pendingInteraction.kind).toBe("url");
757
758 const authorizationUrl = extractUrlInteractionUrl(added.pendingInteraction.payloadJson);
759 const callbackResponse = yield* Effect.promise(() =>
760 fetch(authorizationUrl, {
761 redirect: "follow",
762 }),
763 );
764 expect(callbackResponse.ok).toBe(true);
765 expect(yield* Effect.promise(() => callbackResponse.text())).toContain("Source connected:");
766
767 const completed = yield* waitForExecutionCompletion({
768 client,
769 workspaceId: installation.workspaceId,
770 executionId: added.execution.id,
771 });
772
773 expect(completed.execution.status).toBe("completed");
774 expect(completed.pendingInteraction).toBeNull();
775 expect(completed.execution.resultJson).toContain('"name":"Axiom"');
776 expect(completed.execution.resultJson).toContain('"status":"connected"');
777
778 const sources = yield* client.sources.list({
779 path: {
780 workspaceId: installation.workspaceId,
781 },
782 });
783
784 expect(sources).toHaveLength(1);
785 expect(sources[0]?.name).toBe("Axiom");
786 expect(sources[0]?.status).toBe("connected");
787 expect(sources[0]?.auth.kind).toBe("oauth2");
788
789 const toolCall = yield* client.executions.create({
790 path: {
791 workspaceId: installation.workspaceId,
792 },
793 payload: {
794 code: "return await tools.axiom.whoami({});",
795 },
796 });
797
798 expect(toolCall.execution.status).toBe("completed");
799 expect(toolCall.pendingInteraction).toBeNull();
800 expect(toolCall.execution.resultJson).toContain("oauth-demo");
801 }),
802 );
803
416 it.scoped("returns a typed storage error when a configured MCP endpoint is invalid", () =>804 it.scoped("returns a typed storage error when a configured MCP endpoint is invalid", () =>
417 Effect.gen(function* () {805 Effect.gen(function* () {
418 const server = yield* makeLocalExecutorServer({806 const server = yield* makeLocalExecutorServer({
bun.lock+2−0
@@ -27,9 +27,11 @@ · 26 unmodified lines
27 "devDependencies": {27 "devDependencies": {
28 "@effect/vitest": "catalog:",28 "@effect/vitest": "catalog:",
29 "@executor-v3/mcp-elicitation-demo": "workspace:*",29 "@executor-v3/mcp-elicitation-demo": "workspace:*",
30 "@modelcontextprotocol/sdk": "^1.26.0",
30 "@types/node": "catalog:",31 "@types/node": "catalog:",
31 "bun-types": "catalog:",32 "bun-types": "catalog:",
32 "vitest": "catalog:",33 "vitest": "catalog:",
34 "zod": "catalog:",
33 },35 },
34 },36 },
35 "apps/mcp-elicitation-demo": {37 "apps/mcp-elicitation-demo": {
packages/control-plane/drizzle/20260306094249_large_giant_girl/migration.sql+39−0
@@ -0,0 +1,39 @@
1CREATE TABLE "secret_materials" (
2 "id" text PRIMARY KEY,
3 "purpose" text NOT NULL,
4 "value" text NOT NULL,
5 "created_at" bigint NOT NULL,
6 "updated_at" bigint NOT NULL,
7 CONSTRAINT "secret_materials_purpose_check" CHECK ("purpose" in ('auth_material', 'oauth_access_token', 'oauth_refresh_token', 'oauth_client_info'))
8);
9--> statement-breakpoint
10CREATE TABLE "source_auth_sessions" (
11 "id" text PRIMARY KEY,
12 "workspace_id" text NOT NULL,
13 "source_id" text NOT NULL,
14 "execution_id" text,
15 "interaction_id" text,
16 "strategy" text NOT NULL,
17 "status" text NOT NULL,
18 "endpoint" text NOT NULL,
19 "state" text NOT NULL,
20 "redirect_uri" text NOT NULL,
21 "scope" text,
22 "resource_metadata_url" text,
23 "authorization_server_url" text,
24 "resource_metadata_json" text,
25 "authorization_server_metadata_json" text,
26 "client_information_json" text,
27 "code_verifier" text,
28 "authorization_url" text,
29 "error_text" text,
30 "completed_at" bigint,
31 "created_at" bigint NOT NULL,
32 "updated_at" bigint NOT NULL,
33 CONSTRAINT "source_auth_sessions_strategy_check" CHECK ("strategy" in ('oauth2_authorization_code')),
34 CONSTRAINT "source_auth_sessions_status_check" CHECK ("status" in ('pending', 'completed', 'failed', 'cancelled'))
35);
36--> statement-breakpoint
37CREATE INDEX "secret_materials_updated_idx" ON "secret_materials" ("updated_at","id");--> statement-breakpoint
38CREATE INDEX "source_auth_sessions_workspace_idx" ON "source_auth_sessions" ("workspace_id","updated_at","id");--> statement-breakpoint
39CREATE UNIQUE INDEX "source_auth_sessions_state_idx" ON "source_auth_sessions" ("state");
packages/control-plane/drizzle/20260306094249_large_giant_girl/snapshot.json+2425−0
@@ -0,0 +1,2425 @@
1{
2 "version": "8",
3 "dialect": "postgres",
4 "id": "1141497b-c500-44ff-87db-6a440062becb",
5 "prevIds": [
6 "bac1651d-7a30-4187-a7e2-74bc2b099486"
7 ],
8 "ddl": [
9 {
10 "isRlsEnabled": false,
11 "name": "accounts",
12 "entityType": "tables",
13 "schema": "public"
14 },
15 {
16 "isRlsEnabled": false,
17 "name": "execution_interactions",
18 "entityType": "tables",
19 "schema": "public"
20 },
21 {
22 "isRlsEnabled": false,
23 "name": "executions",
24 "entityType": "tables",
25 "schema": "public"
26 },
27 {
28 "isRlsEnabled": false,
29 "name": "local_installations",
30 "entityType": "tables",
31 "schema": "public"
32 },
33 {
34 "isRlsEnabled": false,
35 "name": "organization_memberships",
36 "entityType": "tables",
37 "schema": "public"
38 },
39 {
40 "isRlsEnabled": false,
41 "name": "organizations",
42 "entityType": "tables",
43 "schema": "public"
44 },
45 {
46 "isRlsEnabled": false,
47 "name": "policies",
48 "entityType": "tables",
49 "schema": "public"
50 },
51 {
52 "isRlsEnabled": false,
53 "name": "secret_materials",
54 "entityType": "tables",
55 "schema": "public"
56 },
57 {
58 "isRlsEnabled": false,
59 "name": "source_auth_sessions",
60 "entityType": "tables",
61 "schema": "public"
62 },
63 {
64 "isRlsEnabled": false,
65 "name": "source_credential_bindings",
66 "entityType": "tables",
67 "schema": "public"
68 },
69 {
70 "isRlsEnabled": false,
71 "name": "sources",
72 "entityType": "tables",
73 "schema": "public"
74 },
75 {
76 "isRlsEnabled": false,
77 "name": "workspaces",
78 "entityType": "tables",
79 "schema": "public"
80 },
81 {
82 "type": "text",
83 "typeSchema": null,
84 "notNull": true,
85 "dimensions": 0,
86 "default": null,
87 "generated": null,
88 "identity": null,
89 "name": "id",
90 "entityType": "columns",
91 "schema": "public",
92 "table": "accounts"
93 },
94 {
95 "type": "text",
96 "typeSchema": null,
97 "notNull": true,
98 "dimensions": 0,
99 "default": null,
100 "generated": null,
101 "identity": null,
102 "name": "provider",
103 "entityType": "columns",
104 "schema": "public",
105 "table": "accounts"
106 },
107 {
108 "type": "text",
109 "typeSchema": null,
110 "notNull": true,
111 "dimensions": 0,
112 "default": null,
113 "generated": null,
114 "identity": null,
115 "name": "subject",
116 "entityType": "columns",
117 "schema": "public",
118 "table": "accounts"
119 },
120 {
121 "type": "text",
122 "typeSchema": null,
123 "notNull": false,
124 "dimensions": 0,
125 "default": null,
126 "generated": null,
127 "identity": null,
128 "name": "email",
129 "entityType": "columns",
130 "schema": "public",
131 "table": "accounts"
132 },
133 {
134 "type": "text",
135 "typeSchema": null,
136 "notNull": false,
137 "dimensions": 0,
138 "default": null,
139 "generated": null,
140 "identity": null,
141 "name": "display_name",
142 "entityType": "columns",
143 "schema": "public",
144 "table": "accounts"
145 },
146 {
147 "type": "bigint",
148 "typeSchema": null,
149 "notNull": true,
150 "dimensions": 0,
151 "default": null,
152 "generated": null,
153 "identity": null,
154 "name": "created_at",
155 "entityType": "columns",
156 "schema": "public",
157 "table": "accounts"
158 },
159 {
160 "type": "bigint",
161 "typeSchema": null,
162 "notNull": true,
163 "dimensions": 0,
164 "default": null,
165 "generated": null,
166 "identity": null,
167 "name": "updated_at",
168 "entityType": "columns",
169 "schema": "public",
170 "table": "accounts"
171 },
172 {
173 "type": "text",
174 "typeSchema": null,
175 "notNull": true,
176 "dimensions": 0,
177 "default": null,
178 "generated": null,
179 "identity": null,
180 "name": "id",
181 "entityType": "columns",
182 "schema": "public",
183 "table": "execution_interactions"
184 },
185 {
186 "type": "text",
187 "typeSchema": null,
188 "notNull": true,
189 "dimensions": 0,
190 "default": null,
191 "generated": null,
192 "identity": null,
193 "name": "execution_id",
194 "entityType": "columns",
195 "schema": "public",
196 "table": "execution_interactions"
197 },
198 {
199 "type": "text",
200 "typeSchema": null,
201 "notNull": true,
202 "dimensions": 0,
203 "default": null,
204 "generated": null,
205 "identity": null,
206 "name": "status",
207 "entityType": "columns",
208 "schema": "public",
209 "table": "execution_interactions"
210 },
211 {
212 "type": "text",
213 "typeSchema": null,
214 "notNull": true,
215 "dimensions": 0,
216 "default": null,
217 "generated": null,
218 "identity": null,
219 "name": "kind",
220 "entityType": "columns",
221 "schema": "public",
222 "table": "execution_interactions"
223 },
224 {
225 "type": "text",
226 "typeSchema": null,
227 "notNull": true,
228 "dimensions": 0,
229 "default": null,
230 "generated": null,
231 "identity": null,
232 "name": "payload_json",
233 "entityType": "columns",
234 "schema": "public",
235 "table": "execution_interactions"
236 },
237 {
238 "type": "text",
239 "typeSchema": null,
240 "notNull": false,
241 "dimensions": 0,
242 "default": null,
243 "generated": null,
244 "identity": null,
245 "name": "response_json",
246 "entityType": "columns",
247 "schema": "public",
248 "table": "execution_interactions"
249 },
250 {
251 "type": "bigint",
252 "typeSchema": null,
253 "notNull": true,
254 "dimensions": 0,
255 "default": null,
256 "generated": null,
257 "identity": null,
258 "name": "created_at",
259 "entityType": "columns",
260 "schema": "public",
261 "table": "execution_interactions"
262 },
263 {
264 "type": "bigint",
265 "typeSchema": null,
266 "notNull": true,
267 "dimensions": 0,
268 "default": null,
269 "generated": null,
270 "identity": null,
271 "name": "updated_at",
272 "entityType": "columns",
273 "schema": "public",
274 "table": "execution_interactions"
275 },
276 {
277 "type": "text",
278 "typeSchema": null,
279 "notNull": true,
280 "dimensions": 0,
281 "default": null,
282 "generated": null,
283 "identity": null,
284 "name": "id",
285 "entityType": "columns",
286 "schema": "public",
287 "table": "executions"
288 },
289 {
290 "type": "text",
291 "typeSchema": null,
292 "notNull": true,
293 "dimensions": 0,
294 "default": null,
295 "generated": null,
296 "identity": null,
297 "name": "workspace_id",
298 "entityType": "columns",
299 "schema": "public",
300 "table": "executions"
301 },
302 {
303 "type": "text",
304 "typeSchema": null,
305 "notNull": true,
306 "dimensions": 0,
307 "default": null,
308 "generated": null,
309 "identity": null,
310 "name": "created_by_account_id",
311 "entityType": "columns",
312 "schema": "public",
313 "table": "executions"
314 },
315 {
316 "type": "text",
317 "typeSchema": null,
318 "notNull": true,
319 "dimensions": 0,
320 "default": null,
321 "generated": null,
322 "identity": null,
323 "name": "status",
324 "entityType": "columns",
325 "schema": "public",
326 "table": "executions"
327 },
328 {
329 "type": "text",
330 "typeSchema": null,
331 "notNull": true,
332 "dimensions": 0,
333 "default": null,
334 "generated": null,
335 "identity": null,
336 "name": "code",
337 "entityType": "columns",
338 "schema": "public",
339 "table": "executions"
340 },
341 {
342 "type": "text",
343 "typeSchema": null,
344 "notNull": false,
345 "dimensions": 0,
346 "default": null,
347 "generated": null,
348 "identity": null,
349 "name": "result_json",
350 "entityType": "columns",
351 "schema": "public",
352 "table": "executions"
353 },
354 {
355 "type": "text",
356 "typeSchema": null,
357 "notNull": false,
358 "dimensions": 0,
359 "default": null,
360 "generated": null,
361 "identity": null,
362 "name": "error_text",
363 "entityType": "columns",
364 "schema": "public",
365 "table": "executions"
366 },
367 {
368 "type": "text",
369 "typeSchema": null,
370 "notNull": false,
371 "dimensions": 0,
372 "default": null,
373 "generated": null,
374 "identity": null,
375 "name": "logs_json",
376 "entityType": "columns",
377 "schema": "public",
378 "table": "executions"
379 },
380 {
381 "type": "bigint",
382 "typeSchema": null,
383 "notNull": false,
384 "dimensions": 0,
385 "default": null,
386 "generated": null,
387 "identity": null,
388 "name": "started_at",
389 "entityType": "columns",
390 "schema": "public",
391 "table": "executions"
392 },
393 {
394 "type": "bigint",
395 "typeSchema": null,
396 "notNull": false,
397 "dimensions": 0,
398 "default": null,
399 "generated": null,
400 "identity": null,
401 "name": "completed_at",
402 "entityType": "columns",
403 "schema": "public",
404 "table": "executions"
405 },
406 {
407 "type": "bigint",
408 "typeSchema": null,
409 "notNull": true,
410 "dimensions": 0,
411 "default": null,
412 "generated": null,
413 "identity": null,
414 "name": "created_at",
415 "entityType": "columns",
416 "schema": "public",
417 "table": "executions"
418 },
419 {
420 "type": "bigint",
421 "typeSchema": null,
422 "notNull": true,
423 "dimensions": 0,
424 "default": null,
425 "generated": null,
426 "identity": null,
427 "name": "updated_at",
428 "entityType": "columns",
429 "schema": "public",
430 "table": "executions"
431 },
432 {
433 "type": "text",
434 "typeSchema": null,
435 "notNull": true,
436 "dimensions": 0,
437 "default": null,
438 "generated": null,
439 "identity": null,
440 "name": "id",
441 "entityType": "columns",
442 "schema": "public",
443 "table": "local_installations"
444 },
445 {
446 "type": "text",
447 "typeSchema": null,
448 "notNull": true,
449 "dimensions": 0,
450 "default": null,
451 "generated": null,
452 "identity": null,
453 "name": "account_id",
454 "entityType": "columns",
455 "schema": "public",
456 "table": "local_installations"
457 },
458 {
459 "type": "text",
460 "typeSchema": null,
461 "notNull": true,
462 "dimensions": 0,
463 "default": null,
464 "generated": null,
465 "identity": null,
466 "name": "organization_id",
467 "entityType": "columns",
468 "schema": "public",
469 "table": "local_installations"
470 },
471 {
472 "type": "text",
473 "typeSchema": null,
474 "notNull": true,
475 "dimensions": 0,
476 "default": null,
477 "generated": null,
478 "identity": null,
479 "name": "workspace_id",
480 "entityType": "columns",
481 "schema": "public",
482 "table": "local_installations"
483 },
484 {
485 "type": "bigint",
486 "typeSchema": null,
487 "notNull": true,
488 "dimensions": 0,
489 "default": null,
490 "generated": null,
491 "identity": null,
492 "name": "created_at",
493 "entityType": "columns",
494 "schema": "public",
495 "table": "local_installations"
496 },
497 {
498 "type": "bigint",
499 "typeSchema": null,
500 "notNull": true,
501 "dimensions": 0,
502 "default": null,
503 "generated": null,
504 "identity": null,
505 "name": "updated_at",
506 "entityType": "columns",
507 "schema": "public",
508 "table": "local_installations"
509 },
510 {
511 "type": "text",
512 "typeSchema": null,
513 "notNull": true,
514 "dimensions": 0,
515 "default": null,
516 "generated": null,
517 "identity": null,
518 "name": "id",
519 "entityType": "columns",
520 "schema": "public",
521 "table": "organization_memberships"
522 },
523 {
524 "type": "text",
525 "typeSchema": null,
526 "notNull": true,
527 "dimensions": 0,
528 "default": null,
529 "generated": null,
530 "identity": null,
531 "name": "organization_id",
532 "entityType": "columns",
533 "schema": "public",
534 "table": "organization_memberships"
535 },
536 {
537 "type": "text",
538 "typeSchema": null,
539 "notNull": true,
540 "dimensions": 0,
541 "default": null,
542 "generated": null,
543 "identity": null,
544 "name": "account_id",
545 "entityType": "columns",
546 "schema": "public",
547 "table": "organization_memberships"
548 },
549 {
550 "type": "text",
551 "typeSchema": null,
552 "notNull": true,
553 "dimensions": 0,
554 "default": null,
555 "generated": null,
556 "identity": null,
557 "name": "role",
558 "entityType": "columns",
559 "schema": "public",
560 "table": "organization_memberships"
561 },
562 {
563 "type": "text",
564 "typeSchema": null,
565 "notNull": true,
566 "dimensions": 0,
567 "default": null,
568 "generated": null,
569 "identity": null,
570 "name": "status",
571 "entityType": "columns",
572 "schema": "public",
573 "table": "organization_memberships"
574 },
575 {
576 "type": "boolean",
577 "typeSchema": null,
578 "notNull": true,
579 "dimensions": 0,
580 "default": null,
581 "generated": null,
582 "identity": null,
583 "name": "billable",
584 "entityType": "columns",
585 "schema": "public",
586 "table": "organization_memberships"
587 },
588 {
589 "type": "text",
590 "typeSchema": null,
591 "notNull": false,
592 "dimensions": 0,
593 "default": null,
594 "generated": null,
595 "identity": null,
596 "name": "invited_by_account_id",
597 "entityType": "columns",
598 "schema": "public",
599 "table": "organization_memberships"
600 },
601 {
602 "type": "bigint",
603 "typeSchema": null,
604 "notNull": false,
605 "dimensions": 0,
606 "default": null,
607 "generated": null,
608 "identity": null,
609 "name": "joined_at",
610 "entityType": "columns",
611 "schema": "public",
612 "table": "organization_memberships"
613 },
614 {
615 "type": "bigint",
616 "typeSchema": null,
617 "notNull": true,
618 "dimensions": 0,
619 "default": null,
620 "generated": null,
621 "identity": null,
622 "name": "created_at",
623 "entityType": "columns",
624 "schema": "public",
625 "table": "organization_memberships"
626 },
627 {
628 "type": "bigint",
629 "typeSchema": null,
630 "notNull": true,
631 "dimensions": 0,
632 "default": null,
633 "generated": null,
634 "identity": null,
635 "name": "updated_at",
636 "entityType": "columns",
637 "schema": "public",
638 "table": "organization_memberships"
639 },
640 {
641 "type": "text",
642 "typeSchema": null,
643 "notNull": true,
644 "dimensions": 0,
645 "default": null,
646 "generated": null,
647 "identity": null,
648 "name": "id",
649 "entityType": "columns",
650 "schema": "public",
651 "table": "organizations"
652 },
653 {
654 "type": "text",
655 "typeSchema": null,
656 "notNull": true,
657 "dimensions": 0,
658 "default": null,
659 "generated": null,
660 "identity": null,
661 "name": "slug",
662 "entityType": "columns",
663 "schema": "public",
664 "table": "organizations"
665 },
666 {
667 "type": "text",
668 "typeSchema": null,
669 "notNull": true,
670 "dimensions": 0,
671 "default": null,
672 "generated": null,
673 "identity": null,
674 "name": "name",
675 "entityType": "columns",
676 "schema": "public",
677 "table": "organizations"
678 },
679 {
680 "type": "text",
681 "typeSchema": null,
682 "notNull": true,
683 "dimensions": 0,
684 "default": null,
685 "generated": null,
686 "identity": null,
687 "name": "status",
688 "entityType": "columns",
689 "schema": "public",
690 "table": "organizations"
691 },
692 {
693 "type": "text",
694 "typeSchema": null,
695 "notNull": false,
696 "dimensions": 0,
697 "default": null,
698 "generated": null,
699 "identity": null,
700 "name": "created_by_account_id",
701 "entityType": "columns",
702 "schema": "public",
703 "table": "organizations"
704 },
705 {
706 "type": "bigint",
707 "typeSchema": null,
708 "notNull": true,
709 "dimensions": 0,
710 "default": null,
711 "generated": null,
712 "identity": null,
713 "name": "created_at",
714 "entityType": "columns",
715 "schema": "public",
716 "table": "organizations"
717 },
718 {
719 "type": "bigint",
720 "typeSchema": null,
721 "notNull": true,
722 "dimensions": 0,
723 "default": null,
724 "generated": null,
725 "identity": null,
726 "name": "updated_at",
727 "entityType": "columns",
728 "schema": "public",
729 "table": "organizations"
730 },
731 {
732 "type": "text",
733 "typeSchema": null,
734 "notNull": true,
735 "dimensions": 0,
736 "default": null,
737 "generated": null,
738 "identity": null,
739 "name": "id",
740 "entityType": "columns",
741 "schema": "public",
742 "table": "policies"
743 },
744 {
745 "type": "text",
746 "typeSchema": null,
747 "notNull": true,
748 "dimensions": 0,
749 "default": null,
750 "generated": null,
751 "identity": null,
752 "name": "workspace_id",
753 "entityType": "columns",
754 "schema": "public",
755 "table": "policies"
756 },
757 {
758 "type": "text",
759 "typeSchema": null,
760 "notNull": false,
761 "dimensions": 0,
762 "default": null,
763 "generated": null,
764 "identity": null,
765 "name": "target_account_id",
766 "entityType": "columns",
767 "schema": "public",
768 "table": "policies"
769 },
770 {
771 "type": "text",
772 "typeSchema": null,
773 "notNull": false,
774 "dimensions": 0,
775 "default": null,
776 "generated": null,
777 "identity": null,
778 "name": "client_id",
779 "entityType": "columns",
780 "schema": "public",
781 "table": "policies"
782 },
783 {
784 "type": "text",
785 "typeSchema": null,
786 "notNull": true,
787 "dimensions": 0,
788 "default": null,
789 "generated": null,
790 "identity": null,
791 "name": "resource_type",
792 "entityType": "columns",
793 "schema": "public",
794 "table": "policies"
795 },
796 {
797 "type": "text",
798 "typeSchema": null,
799 "notNull": true,
800 "dimensions": 0,
801 "default": null,
802 "generated": null,
803 "identity": null,
804 "name": "resource_pattern",
805 "entityType": "columns",
806 "schema": "public",
807 "table": "policies"
808 },
809 {
810 "type": "text",
811 "typeSchema": null,
812 "notNull": true,
813 "dimensions": 0,
814 "default": null,
815 "generated": null,
816 "identity": null,
817 "name": "match_type",
818 "entityType": "columns",
819 "schema": "public",
820 "table": "policies"
821 },
822 {
823 "type": "text",
824 "typeSchema": null,
825 "notNull": true,
826 "dimensions": 0,
827 "default": null,
828 "generated": null,
829 "identity": null,
830 "name": "effect",
831 "entityType": "columns",
832 "schema": "public",
833 "table": "policies"
834 },
835 {
836 "type": "text",
837 "typeSchema": null,
838 "notNull": true,
839 "dimensions": 0,
840 "default": null,
841 "generated": null,
842 "identity": null,
843 "name": "approval_mode",
844 "entityType": "columns",
845 "schema": "public",
846 "table": "policies"
847 },
848 {
849 "type": "text",
850 "typeSchema": null,
851 "notNull": false,
852 "dimensions": 0,
853 "default": null,
854 "generated": null,
855 "identity": null,
856 "name": "argument_conditions_json",
857 "entityType": "columns",
858 "schema": "public",
859 "table": "policies"
860 },
861 {
862 "type": "bigint",
863 "typeSchema": null,
864 "notNull": true,
865 "dimensions": 0,
866 "default": null,
867 "generated": null,
868 "identity": null,
869 "name": "priority",
870 "entityType": "columns",
871 "schema": "public",
872 "table": "policies"
873 },
874 {
875 "type": "boolean",
876 "typeSchema": null,
877 "notNull": true,
878 "dimensions": 0,
879 "default": null,
880 "generated": null,
881 "identity": null,
882 "name": "enabled",
883 "entityType": "columns",
884 "schema": "public",
885 "table": "policies"
886 },
887 {
888 "type": "bigint",
889 "typeSchema": null,
890 "notNull": true,
891 "dimensions": 0,
892 "default": null,
893 "generated": null,
894 "identity": null,
895 "name": "created_at",
896 "entityType": "columns",
897 "schema": "public",
898 "table": "policies"
899 },
900 {
901 "type": "bigint",
902 "typeSchema": null,
903 "notNull": true,
904 "dimensions": 0,
905 "default": null,
906 "generated": null,
907 "identity": null,
908 "name": "updated_at",
909 "entityType": "columns",
910 "schema": "public",
911 "table": "policies"
912 },
913 {
914 "type": "text",
915 "typeSchema": null,
916 "notNull": true,
917 "dimensions": 0,
918 "default": null,
919 "generated": null,
920 "identity": null,
921 "name": "id",
922 "entityType": "columns",
923 "schema": "public",
924 "table": "secret_materials"
925 },
926 {
927 "type": "text",
928 "typeSchema": null,
929 "notNull": true,
930 "dimensions": 0,
931 "default": null,
932 "generated": null,
933 "identity": null,
934 "name": "purpose",
935 "entityType": "columns",
936 "schema": "public",
937 "table": "secret_materials"
938 },
939 {
940 "type": "text",
941 "typeSchema": null,
942 "notNull": true,
943 "dimensions": 0,
944 "default": null,
945 "generated": null,
946 "identity": null,
947 "name": "value",
948 "entityType": "columns",
949 "schema": "public",
950 "table": "secret_materials"
951 },
952 {
953 "type": "bigint",
954 "typeSchema": null,
955 "notNull": true,
956 "dimensions": 0,
957 "default": null,
958 "generated": null,
959 "identity": null,
960 "name": "created_at",
961 "entityType": "columns",
962 "schema": "public",
963 "table": "secret_materials"
964 },
965 {
966 "type": "bigint",
967 "typeSchema": null,
968 "notNull": true,
969 "dimensions": 0,
970 "default": null,
971 "generated": null,
972 "identity": null,
973 "name": "updated_at",
974 "entityType": "columns",
975 "schema": "public",
976 "table": "secret_materials"
977 },
978 {
979 "type": "text",
980 "typeSchema": null,
981 "notNull": true,
982 "dimensions": 0,
983 "default": null,
984 "generated": null,
985 "identity": null,
986 "name": "id",
987 "entityType": "columns",
988 "schema": "public",
989 "table": "source_auth_sessions"
990 },
991 {
992 "type": "text",
993 "typeSchema": null,
994 "notNull": true,
995 "dimensions": 0,
996 "default": null,
997 "generated": null,
998 "identity": null,
999 "name": "workspace_id",
1000 "entityType": "columns",
1001 "schema": "public",
1002 "table": "source_auth_sessions"
1003 },
1004 {
1005 "type": "text",
1006 "typeSchema": null,
1007 "notNull": true,
1008 "dimensions": 0,
1009 "default": null,
1010 "generated": null,
1011 "identity": null,
1012 "name": "source_id",
1013 "entityType": "columns",
1014 "schema": "public",
1015 "table": "source_auth_sessions"
1016 },
1017 {
1018 "type": "text",
1019 "typeSchema": null,
1020 "notNull": false,
1021 "dimensions": 0,
1022 "default": null,
1023 "generated": null,
1024 "identity": null,
1025 "name": "execution_id",
1026 "entityType": "columns",
1027 "schema": "public",
1028 "table": "source_auth_sessions"
1029 },
1030 {
1031 "type": "text",
1032 "typeSchema": null,
1033 "notNull": false,
1034 "dimensions": 0,
1035 "default": null,
1036 "generated": null,
1037 "identity": null,
1038 "name": "interaction_id",
1039 "entityType": "columns",
1040 "schema": "public",
1041 "table": "source_auth_sessions"
1042 },
1043 {
1044 "type": "text",
1045 "typeSchema": null,
1046 "notNull": true,
1047 "dimensions": 0,
1048 "default": null,
1049 "generated": null,
1050 "identity": null,
1051 "name": "strategy",
1052 "entityType": "columns",
1053 "schema": "public",
1054 "table": "source_auth_sessions"
1055 },
1056 {
1057 "type": "text",
1058 "typeSchema": null,
1059 "notNull": true,
1060 "dimensions": 0,
1061 "default": null,
1062 "generated": null,
1063 "identity": null,
1064 "name": "status",
1065 "entityType": "columns",
1066 "schema": "public",
1067 "table": "source_auth_sessions"
1068 },
1069 {
1070 "type": "text",
1071 "typeSchema": null,
1072 "notNull": true,
1073 "dimensions": 0,
1074 "default": null,
1075 "generated": null,
1076 "identity": null,
1077 "name": "endpoint",
1078 "entityType": "columns",
1079 "schema": "public",
1080 "table": "source_auth_sessions"
1081 },
1082 {
1083 "type": "text",
1084 "typeSchema": null,
1085 "notNull": true,
1086 "dimensions": 0,
1087 "default": null,
1088 "generated": null,
1089 "identity": null,
1090 "name": "state",
1091 "entityType": "columns",
1092 "schema": "public",
1093 "table": "source_auth_sessions"
1094 },
1095 {
1096 "type": "text",
1097 "typeSchema": null,
1098 "notNull": true,
1099 "dimensions": 0,
1100 "default": null,
1101 "generated": null,
1102 "identity": null,
1103 "name": "redirect_uri",
1104 "entityType": "columns",
1105 "schema": "public",
1106 "table": "source_auth_sessions"
1107 },
1108 {
1109 "type": "text",
1110 "typeSchema": null,
1111 "notNull": false,
1112 "dimensions": 0,
1113 "default": null,
1114 "generated": null,
1115 "identity": null,
1116 "name": "scope",
1117 "entityType": "columns",
1118 "schema": "public",
1119 "table": "source_auth_sessions"
1120 },
1121 {
1122 "type": "text",
1123 "typeSchema": null,
1124 "notNull": false,
1125 "dimensions": 0,
1126 "default": null,
1127 "generated": null,
1128 "identity": null,
1129 "name": "resource_metadata_url",
1130 "entityType": "columns",
1131 "schema": "public",
1132 "table": "source_auth_sessions"
1133 },
1134 {
1135 "type": "text",
1136 "typeSchema": null,
1137 "notNull": false,
1138 "dimensions": 0,
1139 "default": null,
1140 "generated": null,
1141 "identity": null,
1142 "name": "authorization_server_url",
1143 "entityType": "columns",
1144 "schema": "public",
1145 "table": "source_auth_sessions"
1146 },
1147 {
1148 "type": "text",
1149 "typeSchema": null,
1150 "notNull": false,
1151 "dimensions": 0,
1152 "default": null,
1153 "generated": null,
1154 "identity": null,
1155 "name": "resource_metadata_json",
1156 "entityType": "columns",
1157 "schema": "public",
1158 "table": "source_auth_sessions"
1159 },
1160 {
1161 "type": "text",
1162 "typeSchema": null,
1163 "notNull": false,
1164 "dimensions": 0,
1165 "default": null,
1166 "generated": null,
1167 "identity": null,
1168 "name": "authorization_server_metadata_json",
1169 "entityType": "columns",
1170 "schema": "public",
1171 "table": "source_auth_sessions"
1172 },
1173 {
1174 "type": "text",
1175 "typeSchema": null,
1176 "notNull": false,
1177 "dimensions": 0,
1178 "default": null,
1179 "generated": null,
1180 "identity": null,
1181 "name": "client_information_json",
1182 "entityType": "columns",
1183 "schema": "public",
1184 "table": "source_auth_sessions"
1185 },
1186 {
1187 "type": "text",
1188 "typeSchema": null,
1189 "notNull": false,
1190 "dimensions": 0,
1191 "default": null,
1192 "generated": null,
1193 "identity": null,
1194 "name": "code_verifier",
1195 "entityType": "columns",
1196 "schema": "public",
1197 "table": "source_auth_sessions"
1198 },
1199 {
1200 "type": "text",
1201 "typeSchema": null,
1202 "notNull": false,
1203 "dimensions": 0,
1204 "default": null,
1205 "generated": null,
1206 "identity": null,
1207 "name": "authorization_url",
1208 "entityType": "columns",
1209 "schema": "public",
1210 "table": "source_auth_sessions"
1211 },
1212 {
1213 "type": "text",
1214 "typeSchema": null,
1215 "notNull": false,
1216 "dimensions": 0,
1217 "default": null,
1218 "generated": null,
1219 "identity": null,
1220 "name": "error_text",
1221 "entityType": "columns",
1222 "schema": "public",
1223 "table": "source_auth_sessions"
1224 },
1225 {
1226 "type": "bigint",
1227 "typeSchema": null,
1228 "notNull": false,
1229 "dimensions": 0,
1230 "default": null,
1231 "generated": null,
1232 "identity": null,
1233 "name": "completed_at",
1234 "entityType": "columns",
1235 "schema": "public",
1236 "table": "source_auth_sessions"
1237 },
1238 {
1239 "type": "bigint",
1240 "typeSchema": null,
1241 "notNull": true,
1242 "dimensions": 0,
1243 "default": null,
1244 "generated": null,
1245 "identity": null,
1246 "name": "created_at",
1247 "entityType": "columns",
1248 "schema": "public",
1249 "table": "source_auth_sessions"
1250 },
1251 {
1252 "type": "bigint",
1253 "typeSchema": null,
1254 "notNull": true,
1255 "dimensions": 0,
1256 "default": null,
1257 "generated": null,
1258 "identity": null,
1259 "name": "updated_at",
1260 "entityType": "columns",
1261 "schema": "public",
1262 "table": "source_auth_sessions"
1263 },
1264 {
1265 "type": "text",
1266 "typeSchema": null,
1267 "notNull": true,
1268 "dimensions": 0,
1269 "default": null,
1270 "generated": null,
1271 "identity": null,
1272 "name": "workspace_id",
1273 "entityType": "columns",
1274 "schema": "public",
1275 "table": "source_credential_bindings"
1276 },
1277 {
1278 "type": "text",
1279 "typeSchema": null,
1280 "notNull": true,
1281 "dimensions": 0,
1282 "default": null,
1283 "generated": null,
1284 "identity": null,
1285 "name": "source_id",
1286 "entityType": "columns",
1287 "schema": "public",
1288 "table": "source_credential_bindings"
1289 },
1290 {
1291 "type": "text",
1292 "typeSchema": null,
1293 "notNull": false,
1294 "dimensions": 0,
1295 "default": null,
1296 "generated": null,
1297 "identity": null,
1298 "name": "token_provider_id",
1299 "entityType": "columns",
1300 "schema": "public",
1301 "table": "source_credential_bindings"
1302 },
1303 {
1304 "type": "text",
1305 "typeSchema": null,
1306 "notNull": false,
1307 "dimensions": 0,
1308 "default": null,
1309 "generated": null,
1310 "identity": null,
1311 "name": "token_handle",
1312 "entityType": "columns",
1313 "schema": "public",
1314 "table": "source_credential_bindings"
1315 },
1316 {
1317 "type": "text",
1318 "typeSchema": null,
1319 "notNull": false,
1320 "dimensions": 0,
1321 "default": null,
1322 "generated": null,
1323 "identity": null,
1324 "name": "refresh_token_provider_id",
1325 "entityType": "columns",
1326 "schema": "public",
1327 "table": "source_credential_bindings"
1328 },
1329 {
1330 "type": "text",
1331 "typeSchema": null,
1332 "notNull": false,
1333 "dimensions": 0,
1334 "default": null,
1335 "generated": null,
1336 "identity": null,
1337 "name": "refresh_token_handle",
1338 "entityType": "columns",
1339 "schema": "public",
1340 "table": "source_credential_bindings"
1341 },
1342 {
1343 "type": "bigint",
1344 "typeSchema": null,
1345 "notNull": true,
1346 "dimensions": 0,
1347 "default": null,
1348 "generated": null,
1349 "identity": null,
1350 "name": "created_at",
1351 "entityType": "columns",
1352 "schema": "public",
1353 "table": "source_credential_bindings"
1354 },
1355 {
1356 "type": "bigint",
1357 "typeSchema": null,
1358 "notNull": true,
1359 "dimensions": 0,
1360 "default": null,
1361 "generated": null,
1362 "identity": null,
1363 "name": "updated_at",
1364 "entityType": "columns",
1365 "schema": "public",
1366 "table": "source_credential_bindings"
1367 },
1368 {
1369 "type": "text",
1370 "typeSchema": null,
1371 "notNull": true,
1372 "dimensions": 0,
1373 "default": null,
1374 "generated": null,
1375 "identity": null,
1376 "name": "workspace_id",
1377 "entityType": "columns",
1378 "schema": "public",
1379 "table": "sources"
1380 },
1381 {
1382 "type": "text",
1383 "typeSchema": null,
1384 "notNull": true,
1385 "dimensions": 0,
1386 "default": null,
1387 "generated": null,
1388 "identity": null,
1389 "name": "source_id",
1390 "entityType": "columns",
1391 "schema": "public",
1392 "table": "sources"
1393 },
1394 {
1395 "type": "text",
1396 "typeSchema": null,
1397 "notNull": true,
1398 "dimensions": 0,
1399 "default": null,
1400 "generated": null,
1401 "identity": null,
1402 "name": "name",
1403 "entityType": "columns",
1404 "schema": "public",
1405 "table": "sources"
1406 },
1407 {
1408 "type": "text",
1409 "typeSchema": null,
1410 "notNull": true,
1411 "dimensions": 0,
1412 "default": null,
1413 "generated": null,
1414 "identity": null,
1415 "name": "kind",
1416 "entityType": "columns",
1417 "schema": "public",
1418 "table": "sources"
1419 },
1420 {
1421 "type": "text",
1422 "typeSchema": null,
1423 "notNull": true,
1424 "dimensions": 0,
1425 "default": null,
1426 "generated": null,
1427 "identity": null,
1428 "name": "endpoint",
1429 "entityType": "columns",
1430 "schema": "public",
1431 "table": "sources"
1432 },
1433 {
1434 "type": "text",
1435 "typeSchema": null,
1436 "notNull": true,
1437 "dimensions": 0,
1438 "default": null,
1439 "generated": null,
1440 "identity": null,
1441 "name": "status",
1442 "entityType": "columns",
1443 "schema": "public",
1444 "table": "sources"
1445 },
1446 {
1447 "type": "boolean",
1448 "typeSchema": null,
1449 "notNull": true,
1450 "dimensions": 0,
1451 "default": null,
1452 "generated": null,
1453 "identity": null,
1454 "name": "enabled",
1455 "entityType": "columns",
1456 "schema": "public",
1457 "table": "sources"
1458 },
1459 {
1460 "type": "text",
1461 "typeSchema": null,
1462 "notNull": false,
1463 "dimensions": 0,
1464 "default": null,
1465 "generated": null,
1466 "identity": null,
1467 "name": "namespace",
1468 "entityType": "columns",
1469 "schema": "public",
1470 "table": "sources"
1471 },
1472 {
1473 "type": "text",
1474 "typeSchema": null,
1475 "notNull": false,
1476 "dimensions": 0,
1477 "default": null,
1478 "generated": null,
1479 "identity": null,
1480 "name": "transport",
1481 "entityType": "columns",
1482 "schema": "public",
1483 "table": "sources"
1484 },
1485 {
1486 "type": "text",
1487 "typeSchema": null,
1488 "notNull": false,
1489 "dimensions": 0,
1490 "default": null,
1491 "generated": null,
1492 "identity": null,
1493 "name": "query_params_json",
1494 "entityType": "columns",
1495 "schema": "public",
1496 "table": "sources"
1497 },
1498 {
1499 "type": "text",
1500 "typeSchema": null,
1501 "notNull": false,
1502 "dimensions": 0,
1503 "default": null,
1504 "generated": null,
1505 "identity": null,
1506 "name": "headers_json",
1507 "entityType": "columns",
1508 "schema": "public",
1509 "table": "sources"
1510 },
1511 {
1512 "type": "text",
1513 "typeSchema": null,
1514 "notNull": false,
1515 "dimensions": 0,
1516 "default": null,
1517 "generated": null,
1518 "identity": null,
1519 "name": "spec_url",
1520 "entityType": "columns",
1521 "schema": "public",
1522 "table": "sources"
1523 },
1524 {
1525 "type": "text",
1526 "typeSchema": null,
1527 "notNull": false,
1528 "dimensions": 0,
1529 "default": null,
1530 "generated": null,
1531 "identity": null,
1532 "name": "default_headers_json",
1533 "entityType": "columns",
1534 "schema": "public",
1535 "table": "sources"
1536 },
1537 {
1538 "type": "text",
1539 "typeSchema": null,
1540 "notNull": true,
1541 "dimensions": 0,
1542 "default": null,
1543 "generated": null,
1544 "identity": null,
1545 "name": "auth_kind",
1546 "entityType": "columns",
1547 "schema": "public",
1548 "table": "sources"
1549 },
1550 {
1551 "type": "text",
1552 "typeSchema": null,
1553 "notNull": false,
1554 "dimensions": 0,
1555 "default": null,
1556 "generated": null,
1557 "identity": null,
1558 "name": "auth_header_name",
1559 "entityType": "columns",
1560 "schema": "public",
1561 "table": "sources"
1562 },
1563 {
1564 "type": "text",
1565 "typeSchema": null,
1566 "notNull": false,
1567 "dimensions": 0,
1568 "default": null,
1569 "generated": null,
1570 "identity": null,
1571 "name": "auth_prefix",
1572 "entityType": "columns",
1573 "schema": "public",
1574 "table": "sources"
1575 },
1576 {
1577 "type": "text",
1578 "typeSchema": null,
1579 "notNull": false,
1580 "dimensions": 0,
1581 "default": null,
1582 "generated": null,
1583 "identity": null,
1584 "name": "source_hash",
1585 "entityType": "columns",
1586 "schema": "public",
1587 "table": "sources"
1588 },
1589 {
1590 "type": "text",
1591 "typeSchema": null,
1592 "notNull": false,
1593 "dimensions": 0,
1594 "default": null,
1595 "generated": null,
1596 "identity": null,
1597 "name": "last_error",
1598 "entityType": "columns",
1599 "schema": "public",
1600 "table": "sources"
1601 },
1602 {
1603 "type": "bigint",
1604 "typeSchema": null,
1605 "notNull": true,
1606 "dimensions": 0,
1607 "default": null,
1608 "generated": null,
1609 "identity": null,
1610 "name": "created_at",
1611 "entityType": "columns",
1612 "schema": "public",
1613 "table": "sources"
1614 },
1615 {
1616 "type": "bigint",
1617 "typeSchema": null,
1618 "notNull": true,
1619 "dimensions": 0,
1620 "default": null,
1621 "generated": null,
1622 "identity": null,
1623 "name": "updated_at",
1624 "entityType": "columns",
1625 "schema": "public",
1626 "table": "sources"
1627 },
1628 {
1629 "type": "text",
1630 "typeSchema": null,
1631 "notNull": true,
1632 "dimensions": 0,
1633 "default": null,
1634 "generated": null,
1635 "identity": null,
1636 "name": "id",
1637 "entityType": "columns",
1638 "schema": "public",
1639 "table": "workspaces"
1640 },
1641 {
1642 "type": "text",
1643 "typeSchema": null,
1644 "notNull": true,
1645 "dimensions": 0,
1646 "default": null,
1647 "generated": null,
1648 "identity": null,
1649 "name": "organization_id",
1650 "entityType": "columns",
1651 "schema": "public",
1652 "table": "workspaces"
1653 },
1654 {
1655 "type": "text",
1656 "typeSchema": null,
1657 "notNull": true,
1658 "dimensions": 0,
1659 "default": null,
1660 "generated": null,
1661 "identity": null,
1662 "name": "name",
1663 "entityType": "columns",
1664 "schema": "public",
1665 "table": "workspaces"
1666 },
1667 {
1668 "type": "text",
1669 "typeSchema": null,
1670 "notNull": false,
1671 "dimensions": 0,
1672 "default": null,
1673 "generated": null,
1674 "identity": null,
1675 "name": "created_by_account_id",
1676 "entityType": "columns",
1677 "schema": "public",
1678 "table": "workspaces"
1679 },
1680 {
1681 "type": "bigint",
1682 "typeSchema": null,
1683 "notNull": true,
1684 "dimensions": 0,
1685 "default": null,
1686 "generated": null,
1687 "identity": null,
1688 "name": "created_at",
1689 "entityType": "columns",
1690 "schema": "public",
1691 "table": "workspaces"
1692 },
1693 {
1694 "type": "bigint",
1695 "typeSchema": null,
1696 "notNull": true,
1697 "dimensions": 0,
1698 "default": null,
1699 "generated": null,
1700 "identity": null,
1701 "name": "updated_at",
1702 "entityType": "columns",
1703 "schema": "public",
1704 "table": "workspaces"
1705 },
1706 {
1707 "nameExplicit": true,
1708 "columns": [
1709 {
1710 "value": "provider",
1711 "isExpression": false,
1712 "asc": true,
1713 "nullsFirst": false,
1714 "opclass": null
1715 },
1716 {
1717 "value": "subject",
1718 "isExpression": false,
1719 "asc": true,
1720 "nullsFirst": false,
1721 "opclass": null
1722 }
1723 ],
1724 "isUnique": true,
1725 "where": null,
1726 "with": "",
1727 "method": "btree",
1728 "concurrently": false,
1729 "name": "accounts_provider_subject_idx",
1730 "entityType": "indexes",
1731 "schema": "public",
1732 "table": "accounts"
1733 },
1734 {
1735 "nameExplicit": true,
1736 "columns": [
1737 {
1738 "value": "updated_at",
1739 "isExpression": false,
1740 "asc": true,
1741 "nullsFirst": false,
1742 "opclass": null
1743 },
1744 {
1745 "value": "id",
1746 "isExpression": false,
1747 "asc": true,
1748 "nullsFirst": false,
1749 "opclass": null
1750 }
1751 ],
1752 "isUnique": false,
1753 "where": null,
1754 "with": "",
1755 "method": "btree",
1756 "concurrently": false,
1757 "name": "accounts_updated_idx",
1758 "entityType": "indexes",
1759 "schema": "public",
1760 "table": "accounts"
1761 },
1762 {
1763 "nameExplicit": true,
1764 "columns": [
1765 {
1766 "value": "execution_id",
1767 "isExpression": false,
1768 "asc": true,
1769 "nullsFirst": false,
1770 "opclass": null
1771 },
1772 {
1773 "value": "updated_at",
1774 "isExpression": false,
1775 "asc": true,
1776 "nullsFirst": false,
1777 "opclass": null
1778 },
1779 {
1780 "value": "id",
1781 "isExpression": false,
1782 "asc": true,
1783 "nullsFirst": false,
1784 "opclass": null
1785 }
1786 ],
1787 "isUnique": false,
1788 "where": null,
1789 "with": "",
1790 "method": "btree",
1791 "concurrently": false,
1792 "name": "execution_interactions_execution_idx",
1793 "entityType": "indexes",
1794 "schema": "public",
1795 "table": "execution_interactions"
1796 },
1797 {
1798 "nameExplicit": true,
1799 "columns": [
1800 {
1801 "value": "workspace_id",
1802 "isExpression": false,
1803 "asc": true,
1804 "nullsFirst": false,
1805 "opclass": null
1806 },
1807 {
1808 "value": "updated_at",
1809 "isExpression": false,
1810 "asc": true,
1811 "nullsFirst": false,
1812 "opclass": null
1813 },
1814 {
1815 "value": "id",
1816 "isExpression": false,
1817 "asc": true,
1818 "nullsFirst": false,
1819 "opclass": null
1820 }
1821 ],
1822 "isUnique": false,
1823 "where": null,
1824 "with": "",
1825 "method": "btree",
1826 "concurrently": false,
1827 "name": "executions_workspace_idx",
1828 "entityType": "indexes",
1829 "schema": "public",
1830 "table": "executions"
1831 },
1832 {
1833 "nameExplicit": true,
1834 "columns": [
1835 {
1836 "value": "organization_id",
1837 "isExpression": false,
1838 "asc": true,
1839 "nullsFirst": false,
1840 "opclass": null
1841 }
1842 ],
1843 "isUnique": false,
1844 "where": null,
1845 "with": "",
1846 "method": "btree",
1847 "concurrently": false,
1848 "name": "organization_memberships_org_idx",
1849 "entityType": "indexes",
1850 "schema": "public",
1851 "table": "organization_memberships"
1852 },
1853 {
1854 "nameExplicit": true,
1855 "columns": [
1856 {
1857 "value": "account_id",
1858 "isExpression": false,
1859 "asc": true,
1860 "nullsFirst": false,
1861 "opclass": null
1862 }
1863 ],
1864 "isUnique": false,
1865 "where": null,
1866 "with": "",
1867 "method": "btree",
1868 "concurrently": false,
1869 "name": "organization_memberships_account_idx",
1870 "entityType": "indexes",
1871 "schema": "public",
1872 "table": "organization_memberships"
1873 },
1874 {
1875 "nameExplicit": true,
1876 "columns": [
1877 {
1878 "value": "organization_id",
1879 "isExpression": false,
1880 "asc": true,
1881 "nullsFirst": false,
1882 "opclass": null
1883 },
1884 {
1885 "value": "account_id",
1886 "isExpression": false,
1887 "asc": true,
1888 "nullsFirst": false,
1889 "opclass": null
1890 }
1891 ],
1892 "isUnique": true,
1893 "where": null,
1894 "with": "",
1895 "method": "btree",
1896 "concurrently": false,
1897 "name": "organization_memberships_org_account_idx",
1898 "entityType": "indexes",
1899 "schema": "public",
1900 "table": "organization_memberships"
1901 },
1902 {
1903 "nameExplicit": true,
1904 "columns": [
1905 {
1906 "value": "slug",
1907 "isExpression": false,
1908 "asc": true,
1909 "nullsFirst": false,
1910 "opclass": null
1911 }
1912 ],
1913 "isUnique": true,
1914 "where": null,
1915 "with": "",
1916 "method": "btree",
1917 "concurrently": false,
1918 "name": "organizations_slug_idx",
1919 "entityType": "indexes",
1920 "schema": "public",
1921 "table": "organizations"
1922 },
1923 {
1924 "nameExplicit": true,
1925 "columns": [
1926 {
1927 "value": "updated_at",
1928 "isExpression": false,
1929 "asc": true,
1930 "nullsFirst": false,
1931 "opclass": null
1932 },
1933 {
1934 "value": "id",
1935 "isExpression": false,
1936 "asc": true,
1937 "nullsFirst": false,
1938 "opclass": null
1939 }
1940 ],
1941 "isUnique": false,
1942 "where": null,
1943 "with": "",
1944 "method": "btree",
1945 "concurrently": false,
1946 "name": "organizations_updated_idx",
1947 "entityType": "indexes",
1948 "schema": "public",
1949 "table": "organizations"
1950 },
1951 {
1952 "nameExplicit": true,
1953 "columns": [
1954 {
1955 "value": "workspace_id",
1956 "isExpression": false,
1957 "asc": true,
1958 "nullsFirst": false,
1959 "opclass": null
1960 },
1961 {
1962 "value": "updated_at",
1963 "isExpression": false,
1964 "asc": true,
1965 "nullsFirst": false,
1966 "opclass": null
1967 },
1968 {
1969 "value": "id",
1970 "isExpression": false,
1971 "asc": true,
1972 "nullsFirst": false,
1973 "opclass": null
1974 }
1975 ],
1976 "isUnique": false,
1977 "where": null,
1978 "with": "",
1979 "method": "btree",
1980 "concurrently": false,
1981 "name": "policies_workspace_idx",
1982 "entityType": "indexes",
1983 "schema": "public",
1984 "table": "policies"
1985 },
1986 {
1987 "nameExplicit": true,
1988 "columns": [
1989 {
1990 "value": "updated_at",
1991 "isExpression": false,
1992 "asc": true,
1993 "nullsFirst": false,
1994 "opclass": null
1995 },
1996 {
1997 "value": "id",
1998 "isExpression": false,
1999 "asc": true,
2000 "nullsFirst": false,
2001 "opclass": null
2002 }
2003 ],
2004 "isUnique": false,
2005 "where": null,
2006 "with": "",
2007 "method": "btree",
2008 "concurrently": false,
2009 "name": "secret_materials_updated_idx",
2010 "entityType": "indexes",
2011 "schema": "public",
2012 "table": "secret_materials"
2013 },
2014 {
2015 "nameExplicit": true,
2016 "columns": [
2017 {
2018 "value": "workspace_id",
2019 "isExpression": false,
2020 "asc": true,
2021 "nullsFirst": false,
2022 "opclass": null
2023 },
2024 {
2025 "value": "updated_at",
2026 "isExpression": false,
2027 "asc": true,
2028 "nullsFirst": false,
2029 "opclass": null
2030 },
2031 {
2032 "value": "id",
2033 "isExpression": false,
2034 "asc": true,
2035 "nullsFirst": false,
2036 "opclass": null
2037 }
2038 ],
2039 "isUnique": false,
2040 "where": null,
2041 "with": "",
2042 "method": "btree",
2043 "concurrently": false,
2044 "name": "source_auth_sessions_workspace_idx",
2045 "entityType": "indexes",
2046 "schema": "public",
2047 "table": "source_auth_sessions"
2048 },
2049 {
2050 "nameExplicit": true,
2051 "columns": [
2052 {
2053 "value": "state",
2054 "isExpression": false,
2055 "asc": true,
2056 "nullsFirst": false,
2057 "opclass": null
2058 }
2059 ],
2060 "isUnique": true,
2061 "where": null,
2062 "with": "",
2063 "method": "btree",
2064 "concurrently": false,
2065 "name": "source_auth_sessions_state_idx",
2066 "entityType": "indexes",
2067 "schema": "public",
2068 "table": "source_auth_sessions"
2069 },
2070 {
2071 "nameExplicit": true,
2072 "columns": [
2073 {
2074 "value": "workspace_id",
2075 "isExpression": false,
2076 "asc": true,
2077 "nullsFirst": false,
2078 "opclass": null
2079 },
2080 {
2081 "value": "updated_at",
2082 "isExpression": false,
2083 "asc": true,
2084 "nullsFirst": false,
2085 "opclass": null
2086 },
2087 {
2088 "value": "source_id",
2089 "isExpression": false,
2090 "asc": true,
2091 "nullsFirst": false,
2092 "opclass": null
2093 }
2094 ],
2095 "isUnique": false,
2096 "where": null,
2097 "with": "",
2098 "method": "btree",
2099 "concurrently": false,
2100 "name": "source_credential_bindings_workspace_idx",
2101 "entityType": "indexes",
2102 "schema": "public",
2103 "table": "source_credential_bindings"
2104 },
2105 {
2106 "nameExplicit": true,
2107 "columns": [
2108 {
2109 "value": "workspace_id",
2110 "isExpression": false,
2111 "asc": true,
2112 "nullsFirst": false,
2113 "opclass": null
2114 },
2115 {
2116 "value": "name",
2117 "isExpression": false,
2118 "asc": true,
2119 "nullsFirst": false,
2120 "opclass": null
2121 }
2122 ],
2123 "isUnique": true,
2124 "where": null,
2125 "with": "",
2126 "method": "btree",
2127 "concurrently": false,
2128 "name": "sources_workspace_name_idx",
2129 "entityType": "indexes",
2130 "schema": "public",
2131 "table": "sources"
2132 },
2133 {
2134 "nameExplicit": true,
2135 "columns": [
2136 {
2137 "value": "organization_id",
2138 "isExpression": false,
2139 "asc": true,
2140 "nullsFirst": false,
2141 "opclass": null
2142 }
2143 ],
2144 "isUnique": false,
2145 "where": null,
2146 "with": "",
2147 "method": "btree",
2148 "concurrently": false,
2149 "name": "workspaces_org_idx",
2150 "entityType": "indexes",
2151 "schema": "public",
2152 "table": "workspaces"
2153 },
2154 {
2155 "nameExplicit": true,
2156 "columns": [
2157 {
2158 "value": "organization_id",
2159 "isExpression": false,
2160 "asc": true,
2161 "nullsFirst": false,
2162 "opclass": null
2163 },
2164 {
2165 "value": "name",
2166 "isExpression": false,
2167 "asc": true,
2168 "nullsFirst": false,
2169 "opclass": null
2170 }
2171 ],
2172 "isUnique": true,
2173 "where": null,
2174 "with": "",
2175 "method": "btree",
2176 "concurrently": false,
2177 "name": "workspaces_org_name_idx",
2178 "entityType": "indexes",
2179 "schema": "public",
2180 "table": "workspaces"
2181 },
2182 {
2183 "columns": [
2184 "workspace_id",
2185 "source_id"
2186 ],
2187 "nameExplicit": false,
2188 "name": "source_credential_bindings_pkey",
2189 "entityType": "pks",
2190 "schema": "public",
2191 "table": "source_credential_bindings"
2192 },
2193 {
2194 "columns": [
2195 "workspace_id",
2196 "source_id"
2197 ],
2198 "nameExplicit": false,
2199 "name": "sources_pkey",
2200 "entityType": "pks",
2201 "schema": "public",
2202 "table": "sources"
2203 },
2204 {
2205 "columns": [
2206 "id"
2207 ],
2208 "nameExplicit": false,
2209 "name": "accounts_pkey",
2210 "schema": "public",
2211 "table": "accounts",
2212 "entityType": "pks"
2213 },
2214 {
2215 "columns": [
2216 "id"
2217 ],
2218 "nameExplicit": false,
2219 "name": "execution_interactions_pkey",
2220 "schema": "public",
2221 "table": "execution_interactions",
2222 "entityType": "pks"
2223 },
2224 {
2225 "columns": [
2226 "id"
2227 ],
2228 "nameExplicit": false,
2229 "name": "executions_pkey",
2230 "schema": "public",
2231 "table": "executions",
2232 "entityType": "pks"
2233 },
2234 {
2235 "columns": [
2236 "id"
2237 ],
2238 "nameExplicit": false,
2239 "name": "local_installations_pkey",
2240 "schema": "public",
2241 "table": "local_installations",
2242 "entityType": "pks"
2243 },
2244 {
2245 "columns": [
2246 "id"
2247 ],
2248 "nameExplicit": false,
2249 "name": "organization_memberships_pkey",
2250 "schema": "public",
2251 "table": "organization_memberships",
2252 "entityType": "pks"
2253 },
2254 {
2255 "columns": [
2256 "id"
2257 ],
2258 "nameExplicit": false,
2259 "name": "organizations_pkey",
2260 "schema": "public",
2261 "table": "organizations",
2262 "entityType": "pks"
2263 },
2264 {
2265 "columns": [
2266 "id"
2267 ],
2268 "nameExplicit": false,
2269 "name": "policies_pkey",
2270 "schema": "public",
2271 "table": "policies",
2272 "entityType": "pks"
2273 },
2274 {
2275 "columns": [
2276 "id"
2277 ],
2278 "nameExplicit": false,
2279 "name": "secret_materials_pkey",
2280 "schema": "public",
2281 "table": "secret_materials",
2282 "entityType": "pks"
2283 },
2284 {
2285 "columns": [
2286 "id"
2287 ],
2288 "nameExplicit": false,
2289 "name": "source_auth_sessions_pkey",
2290 "schema": "public",
2291 "table": "source_auth_sessions",
2292 "entityType": "pks"
2293 },
2294 {
2295 "columns": [
2296 "id"
2297 ],
2298 "nameExplicit": false,
2299 "name": "workspaces_pkey",
2300 "schema": "public",
2301 "table": "workspaces",
2302 "entityType": "pks"
2303 },
2304 {
2305 "value": "\"provider\" in ('local', 'workos', 'service')",
2306 "name": "accounts_provider_check",
2307 "entityType": "checks",
2308 "schema": "public",
2309 "table": "accounts"
2310 },
2311 {
2312 "value": "\"status\" in ('pending', 'resolved', 'cancelled')",
2313 "name": "execution_interactions_status_check",
2314 "entityType": "checks",
2315 "schema": "public",
2316 "table": "execution_interactions"
2317 },
2318 {
2319 "value": "\"status\" in ('pending', 'running', 'waiting_for_interaction', 'completed', 'failed', 'cancelled')",
2320 "name": "executions_status_check",
2321 "entityType": "checks",
2322 "schema": "public",
2323 "table": "executions"
2324 },
2325 {
2326 "value": "\"role\" in ('viewer', 'editor', 'admin', 'owner')",
2327 "name": "organization_memberships_role_check",
2328 "entityType": "checks",
2329 "schema": "public",
2330 "table": "organization_memberships"
2331 },
2332 {
2333 "value": "\"status\" in ('invited', 'active', 'suspended', 'removed')",
2334 "name": "organization_memberships_status_check",
2335 "entityType": "checks",
2336 "schema": "public",
2337 "table": "organization_memberships"
2338 },
2339 {
2340 "value": "\"status\" in ('active', 'suspended', 'archived')",
2341 "name": "organizations_status_check",
2342 "entityType": "checks",
2343 "schema": "public",
2344 "table": "organizations"
2345 },
2346 {
2347 "value": "\"resource_type\" in ('all_tools', 'source', 'namespace', 'tool_path')",
2348 "name": "policies_resource_type_check",
2349 "entityType": "checks",
2350 "schema": "public",
2351 "table": "policies"
2352 },
2353 {
2354 "value": "\"match_type\" in ('glob', 'exact')",
2355 "name": "policies_match_type_check",
2356 "entityType": "checks",
2357 "schema": "public",
2358 "table": "policies"
2359 },
2360 {
2361 "value": "\"effect\" in ('allow', 'deny')",
2362 "name": "policies_effect_check",
2363 "entityType": "checks",
2364 "schema": "public",
2365 "table": "policies"
2366 },
2367 {
2368 "value": "\"approval_mode\" in ('auto', 'required')",
2369 "name": "policies_approval_mode_check",
2370 "entityType": "checks",
2371 "schema": "public",
2372 "table": "policies"
2373 },
2374 {
2375 "value": "\"purpose\" in ('auth_material', 'oauth_access_token', 'oauth_refresh_token', 'oauth_client_info')",
2376 "name": "secret_materials_purpose_check",
2377 "entityType": "checks",
2378 "schema": "public",
2379 "table": "secret_materials"
2380 },
2381 {
2382 "value": "\"strategy\" in ('oauth2_authorization_code')",
2383 "name": "source_auth_sessions_strategy_check",
2384 "entityType": "checks",
2385 "schema": "public",
2386 "table": "source_auth_sessions"
2387 },
2388 {
2389 "value": "\"status\" in ('pending', 'completed', 'failed', 'cancelled')",
2390 "name": "source_auth_sessions_status_check",
2391 "entityType": "checks",
2392 "schema": "public",
2393 "table": "source_auth_sessions"
2394 },
2395 {
2396 "value": "\"kind\" in ('mcp', 'openapi', 'graphql', 'internal')",
2397 "name": "sources_kind_check",
2398 "entityType": "checks",
2399 "schema": "public",
2400 "table": "sources"
2401 },
2402 {
2403 "value": "\"status\" in ('draft', 'probing', 'auth_required', 'connected', 'error')",
2404 "name": "sources_status_check",
2405 "entityType": "checks",
2406 "schema": "public",
2407 "table": "sources"
2408 },
2409 {
2410 "value": "\"transport\" is null or \"transport\" in ('auto', 'streamable-http', 'sse')",
2411 "name": "sources_transport_check",
2412 "entityType": "checks",
2413 "schema": "public",
2414 "table": "sources"
2415 },
2416 {
2417 "value": "\"auth_kind\" in ('none', 'bearer', 'oauth2')",
2418 "name": "sources_auth_kind_check",
2419 "entityType": "checks",
2420 "schema": "public",
2421 "table": "sources"
2422 }
2423 ],
2424 "renames": []
2425}
packages/control-plane/src/api/local/api.ts+10−0
@@ -1,7 +1,9 @@
1import { HttpApiEndpoint, HttpApiGroup } from "@effect/platform";1import { HttpApiEndpoint, HttpApiGroup } from "@effect/platform";
2import * as Schema from "effect/Schema";
2import { LocalInstallationSchema } from "#schema";3import { LocalInstallationSchema } from "#schema";
3 4
4import {5import {
6 ControlPlaneBadRequestError,
5 ControlPlaneNotFoundError,7 ControlPlaneNotFoundError,
6 ControlPlaneStorageError,8 ControlPlaneStorageError,
7} from "../errors";9} from "../errors";
@@ -10,6 +12,14 @@ export class LocalApi extends HttpApiGroup.make("local") · 2 unmodified lines
10 .add(12 .add(
11 HttpApiEndpoint.get("installation")`/local/installation`13 HttpApiEndpoint.get("installation")`/local/installation`
12 .addSuccess(LocalInstallationSchema)14 .addSuccess(LocalInstallationSchema)
15 .addError(ControlPlaneBadRequestError)
16 .addError(ControlPlaneNotFoundError)
17 .addError(ControlPlaneStorageError),
18 )
19 .add(
20 HttpApiEndpoint.get("oauthCallback")`/local/oauth/callback`
21 .addSuccess(Schema.String)
22 .addError(ControlPlaneBadRequestError)
13 .addError(ControlPlaneNotFoundError)23 .addError(ControlPlaneNotFoundError)
14 .addError(ControlPlaneStorageError),24 .addError(ControlPlaneStorageError),
15 )25 )
packages/control-plane/src/api/local/http.ts+24−7
@@ -1,4 +1,4 @@
1import { HttpApiBuilder } from "@effect/platform";1import { HttpApiBuilder, HttpServerRequest } from "@effect/platform";
2import * as Effect from "effect/Effect";2import * as Effect from "effect/Effect";
3 3
4import { ControlPlaneApi } from "../api";4import { ControlPlaneApi } from "../api";
@@ -8,10 +8,27 @@ export const ControlPlaneLocalLive = HttpApiBuilder.group( · 3 unmodified lines
8 ControlPlaneApi,8 ControlPlaneApi,
9 "local",9 "local",
10 (handlers) =>10 (handlers) =>
11 handlers.handle("installation", () =>11 handlers
12 Effect.gen(function* () {12 .handle("installation", () =>
13 const service = yield* ControlPlaneService;13 Effect.gen(function* () {
14 return yield* service.getLocalInstallation();14 const service = yield* ControlPlaneService;
15 })15 return yield* service.getLocalInstallation();
16 ),16 }),
17 )
18 .handle("oauthCallback", () =>
19 Effect.gen(function* () {
20 const service = yield* ControlPlaneService;
21 const request = yield* HttpServerRequest.HttpServerRequest;
22 const requestUrl = new URL(request.url, "http://127.0.0.1");
23
24 const source = yield* service.completeSourceAuthCallback({
25 state: requestUrl.searchParams.get("state") ?? "",
26 code: requestUrl.searchParams.get("code"),
27 error: requestUrl.searchParams.get("error"),
28 errorDescription: requestUrl.searchParams.get("error_description"),
29 });
30
31 return `Source connected: ${source.id}. You can close this window.`;
32 }),
33 ),
17);34);
packages/control-plane/src/api/service.ts+10−0
@@ -112,6 +112,13 @@ export type RemoveSourceInput = { · 111 unmodified lines
112 sourceId: SourceId;112 sourceId: SourceId;
113};113};
114 114
115export type CompleteSourceAuthCallbackInput = {
116 state: string;
117 code?: string | null;
118 error?: string | null;
119 errorDescription?: string | null;
120};
121
115export type CreatePolicyInput = {122export type CreatePolicyInput = {
116 workspaceId: WorkspaceId;123 workspaceId: WorkspaceId;
117 payload: CreatePolicyPayload;124 payload: CreatePolicyPayload;
@@ -255,6 +262,9 @@ export type ControlPlaneServiceShape = { · 137 unmodified lines
255 LocalInstallation,262 LocalInstallation,
256 ControlPlaneNotFoundError | ControlPlaneStorageError263 ControlPlaneNotFoundError | ControlPlaneStorageError
257 >;264 >;
265 completeSourceAuthCallback: (
266 input: CompleteSourceAuthCallbackInput,
267 ) => Effect.Effect<Source, ControlPlaneBadRequestError | ControlPlaneNotFoundError | ControlPlaneStorageError>;
258 268
259 listPolicies: (269 listPolicies: (
260 workspaceId: WorkspaceId,270 workspaceId: WorkspaceId,
packages/control-plane/src/persistence/control-plane-rows.ts+4−0
@@ -8,7 +8,9 @@ import { · 7 unmodified lines
8 createOrganizationMembershipsRepo,8 createOrganizationMembershipsRepo,
9 createOrganizationsRepo,9 createOrganizationsRepo,
10 createPoliciesRepo,10 createPoliciesRepo,
11 createSecretMaterialsRepo,
11 createSourceCredentialBindingsRepo,12 createSourceCredentialBindingsRepo,
13 createSourceAuthSessionsRepo,
12 createSourcesRepo,14 createSourcesRepo,
13 createWorkspacesRepo,15 createWorkspacesRepo,
14} from "./repos";16} from "./repos";
@@ -28,6 +30,8 @@ export const createControlPlaneRows = ({ · 13 unmodified lines
28 workspaces: createWorkspacesRepo(client, tables),30 workspaces: createWorkspacesRepo(client, tables),
29 sources: createSourcesRepo(client, tables),31 sources: createSourcesRepo(client, tables),
30 sourceCredentialBindings: createSourceCredentialBindingsRepo(client, tables),32 sourceCredentialBindings: createSourceCredentialBindingsRepo(client, tables),
33 secretMaterials: createSecretMaterialsRepo(client, tables),
34 sourceAuthSessions: createSourceAuthSessionsRepo(client, tables),
31 policies: createPoliciesRepo(client, tables),35 policies: createPoliciesRepo(client, tables),
32 localInstallations: createLocalInstallationsRepo(client, tables),36 localInstallations: createLocalInstallationsRepo(client, tables),
33 executions: createExecutionsRepo(client, tables),37 executions: createExecutionsRepo(client, tables),
packages/control-plane/src/persistence/repos/index.ts+2−0
@@ -4,6 +4,8 @@ export { createOrganizationMembershipsRepo } from "./organization-memberships-re · 3 unmodified lines
4export { createWorkspacesRepo } from "./workspaces-repo";4export { createWorkspacesRepo } from "./workspaces-repo";
5export { createSourcesRepo } from "./sources-repo";5export { createSourcesRepo } from "./sources-repo";
6export { createSourceCredentialBindingsRepo } from "./source-credential-bindings-repo";6export { createSourceCredentialBindingsRepo } from "./source-credential-bindings-repo";
7export { createSecretMaterialsRepo } from "./secret-materials-repo";
8export { createSourceAuthSessionsRepo } from "./source-auth-sessions-repo";
7export { createPoliciesRepo } from "./policies-repo";9export { createPoliciesRepo } from "./policies-repo";
8export { createLocalInstallationsRepo } from "./local-installations-repo";10export { createLocalInstallationsRepo } from "./local-installations-repo";
9export { createExecutionsRepo } from "./executions-repo";11export { createExecutionsRepo } from "./executions-repo";
packages/control-plane/src/persistence/repos/secret-materials-repo.ts+55−0
@@ -0,0 +1,55 @@
1import {
2 type SecretMaterial,
3 SecretMaterialSchema,
4} from "#schema";
5import * as Option from "effect/Option";
6import { Schema } from "effect";
7import { eq } from "drizzle-orm";
8
9import type { DrizzleClient } from "../client";
10import type { DrizzleTables } from "../schema";
11import { firstOption, withoutCreatedAt } from "./shared";
12
13const decodeSecretMaterial = Schema.decodeUnknownSync(SecretMaterialSchema);
14
15export const createSecretMaterialsRepo = (
16 client: DrizzleClient,
17 tables: DrizzleTables,
18) => ({
19 getById: (id: SecretMaterial["id"]) =>
20 client.use("rows.secret_materials.get_by_id", async (db) => {
21 const rows = await db
22 .select()
23 .from(tables.secretMaterialsTable)
24 .where(eq(tables.secretMaterialsTable.id, id))
25 .limit(1);
26
27 const row = firstOption(rows);
28 return Option.isSome(row)
29 ? Option.some(decodeSecretMaterial(row.value))
30 : Option.none<SecretMaterial>();
31 }),
32
33 upsert: (material: SecretMaterial) =>
34 client.use("rows.secret_materials.upsert", async (db) => {
35 await db
36 .insert(tables.secretMaterialsTable)
37 .values(material)
38 .onConflictDoUpdate({
39 target: [tables.secretMaterialsTable.id],
40 set: {
41 ...withoutCreatedAt(material),
42 },
43 });
44 }),
45
46 removeById: (id: SecretMaterial["id"]) =>
47 client.use("rows.secret_materials.remove", async (db) => {
48 const deleted = await db
49 .delete(tables.secretMaterialsTable)
50 .where(eq(tables.secretMaterialsTable.id, id))
51 .returning();
52
53 return deleted.length > 0;
54 }),
55});
packages/control-plane/src/persistence/repos/source-auth-sessions-repo.ts+153−0
@@ -0,0 +1,153 @@
1import {
2 type SourceAuthSession,
3 SourceAuthSessionSchema,
4} from "#schema";
5import * as Option from "effect/Option";
6import { Schema } from "effect";
7import { and, asc, eq } from "drizzle-orm";
8
9import type { DrizzleClient } from "../client";
10import type { DrizzleTables } from "../schema";
11import { firstOption, withoutCreatedAt } from "./shared";
12
13const decodeSourceAuthSession = Schema.decodeUnknownSync(SourceAuthSessionSchema);
14
15const toUpdateSet = (
16 patch: Partial<Omit<SourceAuthSession, "id" | "workspaceId" | "sourceId" | "createdAt">>,
17): Partial<DrizzleTables["sourceAuthSessionsTable"]["$inferInsert"]> => {
18 const set: Partial<DrizzleTables["sourceAuthSessionsTable"]["$inferInsert"]> = {};
19
20 if (patch.executionId !== undefined) set.executionId = patch.executionId;
21 if (patch.interactionId !== undefined) set.interactionId = patch.interactionId;
22 if (patch.strategy !== undefined) set.strategy = patch.strategy;
23 if (patch.status !== undefined) set.status = patch.status;
24 if (patch.endpoint !== undefined) set.endpoint = patch.endpoint;
25 if (patch.state !== undefined) set.state = patch.state;
26 if (patch.redirectUri !== undefined) set.redirectUri = patch.redirectUri;
27 if (patch.scope !== undefined) set.scope = patch.scope;
28 if (patch.resourceMetadataUrl !== undefined) {
29 set.resourceMetadataUrl = patch.resourceMetadataUrl;
30 }
31 if (patch.authorizationServerUrl !== undefined) {
32 set.authorizationServerUrl = patch.authorizationServerUrl;
33 }
34 if (patch.resourceMetadataJson !== undefined) {
35 set.resourceMetadataJson = patch.resourceMetadataJson;
36 }
37 if (patch.authorizationServerMetadataJson !== undefined) {
38 set.authorizationServerMetadataJson = patch.authorizationServerMetadataJson;
39 }
40 if (patch.clientInformationJson !== undefined) {
41 set.clientInformationJson = patch.clientInformationJson;
42 }
43 if (patch.codeVerifier !== undefined) set.codeVerifier = patch.codeVerifier;
44 if (patch.authorizationUrl !== undefined) set.authorizationUrl = patch.authorizationUrl;
45 if (patch.errorText !== undefined) set.errorText = patch.errorText;
46 if (patch.completedAt !== undefined) set.completedAt = patch.completedAt;
47 if (patch.updatedAt !== undefined) set.updatedAt = patch.updatedAt;
48
49 return set;
50};
51
52export const createSourceAuthSessionsRepo = (
53 client: DrizzleClient,
54 tables: DrizzleTables,
55) => ({
56 listByWorkspaceId: (workspaceId: SourceAuthSession["workspaceId"]) =>
57 client.use("rows.source_auth_sessions.list_by_workspace", async (db) => {
58 const rows = await db
59 .select()
60 .from(tables.sourceAuthSessionsTable)
61 .where(eq(tables.sourceAuthSessionsTable.workspaceId, workspaceId))
62 .orderBy(asc(tables.sourceAuthSessionsTable.updatedAt), asc(tables.sourceAuthSessionsTable.id));
63
64 return rows.map((row) => decodeSourceAuthSession(row));
65 }),
66
67 getById: (id: SourceAuthSession["id"]) =>
68 client.use("rows.source_auth_sessions.get_by_id", async (db) => {
69 const rows = await db
70 .select()
71 .from(tables.sourceAuthSessionsTable)
72 .where(eq(tables.sourceAuthSessionsTable.id, id))
73 .limit(1);
74
75 const row = firstOption(rows);
76 return Option.isSome(row)
77 ? Option.some(decodeSourceAuthSession(row.value))
78 : Option.none<SourceAuthSession>();
79 }),
80
81 getByState: (state: SourceAuthSession["state"]) =>
82 client.use("rows.source_auth_sessions.get_by_state", async (db) => {
83 const rows = await db
84 .select()
85 .from(tables.sourceAuthSessionsTable)
86 .where(eq(tables.sourceAuthSessionsTable.state, state))
87 .limit(1);
88
89 const row = firstOption(rows);
90 return Option.isSome(row)
91 ? Option.some(decodeSourceAuthSession(row.value))
92 : Option.none<SourceAuthSession>();
93 }),
94
95 getPendingByWorkspaceAndSourceId: (
96 workspaceId: SourceAuthSession["workspaceId"],
97 sourceId: SourceAuthSession["sourceId"],
98 ) =>
99 client.use("rows.source_auth_sessions.get_pending_by_workspace_and_source_id", async (db) => {
100 const rows = await db
101 .select()
102 .from(tables.sourceAuthSessionsTable)
103 .where(
104 and(
105 eq(tables.sourceAuthSessionsTable.workspaceId, workspaceId),
106 eq(tables.sourceAuthSessionsTable.sourceId, sourceId),
107 eq(tables.sourceAuthSessionsTable.status, "pending"),
108 ),
109 )
110 .orderBy(asc(tables.sourceAuthSessionsTable.updatedAt), asc(tables.sourceAuthSessionsTable.id))
111 .limit(1);
112
113 const row = firstOption(rows);
114 return Option.isSome(row)
115 ? Option.some(decodeSourceAuthSession(row.value))
116 : Option.none<SourceAuthSession>();
117 }),
118
119 insert: (session: SourceAuthSession) =>
120 client.use("rows.source_auth_sessions.insert", async (db) => {
121 await db.insert(tables.sourceAuthSessionsTable).values(session);
122 }),
123
124 update: (
125 id: SourceAuthSession["id"],
126 patch: Partial<Omit<SourceAuthSession, "id" | "workspaceId" | "sourceId" | "createdAt">>,
127 ) =>
128 client.use("rows.source_auth_sessions.update", async (db) => {
129 const rows = await db
130 .update(tables.sourceAuthSessionsTable)
131 .set(toUpdateSet(patch))
132 .where(eq(tables.sourceAuthSessionsTable.id, id))
133 .returning();
134
135 const row = firstOption(rows);
136 return Option.isSome(row)
137 ? Option.some(decodeSourceAuthSession(row.value))
138 : Option.none<SourceAuthSession>();
139 }),
140
141 upsert: (session: SourceAuthSession) =>
142 client.use("rows.source_auth_sessions.upsert", async (db) => {
143 await db
144 .insert(tables.sourceAuthSessionsTable)
145 .values(session)
146 .onConflictDoUpdate({
147 target: [tables.sourceAuthSessionsTable.id],
148 set: {
149 ...withoutCreatedAt(session),
150 },
151 });
152 }),
153});
packages/control-plane/src/persistence/schema.ts+66−0
@@ -17,6 +17,8 @@ export const tableNames = { · 16 unmodified lines
17 workspaces: "workspaces",17 workspaces: "workspaces",
18 sources: "sources",18 sources: "sources",
19 sourceCredentialBindings: "source_credential_bindings",19 sourceCredentialBindings: "source_credential_bindings",
20 secretMaterials: "secret_materials",
21 sourceAuthSessions: "source_auth_sessions",
20 policies: "policies",22 policies: "policies",
21 localInstallations: "local_installations",23 localInstallations: "local_installations",
22 executions: "executions",24 executions: "executions",
@@ -181,6 +183,68 @@ export const sourceCredentialBindingsTable = pgTable( · 158 unmodified lines
181 ],183 ],
182);184);
183 185
186export const secretMaterialsTable = pgTable(
187 tableNames.secretMaterials,
188 {
189 id: text("id").notNull().primaryKey(),
190 purpose: text("purpose").notNull(),
191 value: text("value").notNull(),
192 createdAt: bigint("created_at", { mode: "number" }).notNull(),
193 updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
194 },
195 (table) => [
196 index("secret_materials_updated_idx").on(table.updatedAt, table.id),
197 check(
198 "secret_materials_purpose_check",
199 sql`${table.purpose} in ('auth_material', 'oauth_access_token', 'oauth_refresh_token', 'oauth_client_info')`,
200 ),
201 ],
202);
203
204export const sourceAuthSessionsTable = pgTable(
205 tableNames.sourceAuthSessions,
206 {
207 id: text("id").notNull().primaryKey(),
208 workspaceId: text("workspace_id").notNull(),
209 sourceId: text("source_id").notNull(),
210 executionId: text("execution_id"),
211 interactionId: text("interaction_id"),
212 strategy: text("strategy").notNull(),
213 status: text("status").notNull(),
214 endpoint: text("endpoint").notNull(),
215 state: text("state").notNull(),
216 redirectUri: text("redirect_uri").notNull(),
217 scope: text("scope"),
218 resourceMetadataUrl: text("resource_metadata_url"),
219 authorizationServerUrl: text("authorization_server_url"),
220 resourceMetadataJson: text("resource_metadata_json"),
221 authorizationServerMetadataJson: text("authorization_server_metadata_json"),
222 clientInformationJson: text("client_information_json"),
223 codeVerifier: text("code_verifier"),
224 authorizationUrl: text("authorization_url"),
225 errorText: text("error_text"),
226 completedAt: bigint("completed_at", { mode: "number" }),
227 createdAt: bigint("created_at", { mode: "number" }).notNull(),
228 updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
229 },
230 (table) => [
231 index("source_auth_sessions_workspace_idx").on(
232 table.workspaceId,
233 table.updatedAt,
234 table.id,
235 ),
236 uniqueIndex("source_auth_sessions_state_idx").on(table.state),
237 check(
238 "source_auth_sessions_strategy_check",
239 sql`${table.strategy} in ('oauth2_authorization_code')`,
240 ),
241 check(
242 "source_auth_sessions_status_check",
243 sql`${table.status} in ('pending', 'completed', 'failed', 'cancelled')`,
244 ),
245 ],
246);
247
184export const policiesTable = pgTable(248export const policiesTable = pgTable(
185 tableNames.policies,249 tableNames.policies,
186 {250 {
@@ -286,6 +350,8 @@ export const drizzleSchema = { · 99 unmodified lines
286 workspacesTable,350 workspacesTable,
287 sourcesTable,351 sourcesTable,
288 sourceCredentialBindingsTable,352 sourceCredentialBindingsTable,
353 secretMaterialsTable,
354 sourceAuthSessionsTable,
289 policiesTable,355 policiesTable,
290 localInstallationsTable,356 localInstallationsTable,
291 executionsTable,357 executionsTable,
packages/control-plane/src/runtime/execution-service.ts+32−1
@@ -4,6 +4,7 @@ import { · 3 unmodified lines
4 ControlPlaneNotFoundError,4 ControlPlaneNotFoundError,
5 ControlPlaneStorageError,5 ControlPlaneStorageError,
6} from "#api";6} from "#api";
7import type { ToolInvoker } from "@executor-v3/codemode-core";
7import {8import {
8 ControlPlanePersistenceError,9 ControlPlanePersistenceError,
9 type SqlControlPlaneRows,10 type SqlControlPlaneRows,
@@ -109,6 +110,32 @@ const defaultExecutionResolver: ResolveExecutionEnvironment = () => · 99 unmodified lines
109 110
110const defaultLiveExecutionManager = makeLiveExecutionManager();111const defaultLiveExecutionManager = makeLiveExecutionManager();
111 112
113const withExecutionInvocationContext = (input: {
114 executionId: Execution["id"];
115 toolInvoker: ToolInvoker;
116}): ToolInvoker => {
117 let sequence = 0;
118
119 return {
120 invoke: ({ path, args, context }) => {
121 sequence += 1;
122
123 return input.toolInvoker.invoke({
124 path,
125 args,
126 context: {
127 ...context,
128 runId: input.executionId,
129 callId:
130 typeof context?.callId === "string" && context.callId.length > 0
131 ? context.callId
132 : `call_${String(sequence)}`,
133 },
134 });
135 },
136 };
137};
138
112const fetchExecution = (139const fetchExecution = (
113 rows: SqlControlPlaneRows,140 rows: SqlControlPlaneRows,
114 input: {141 input: {
@@ -229,10 +256,14 @@ export const makeRuntimeExecutionsService = ( · 114 unmodified lines
229 );256 );
230 257
231 const nextState = yield* liveExecutionManager.registerStateWaiter(execution.id);258 const nextState = yield* liveExecutionManager.registerStateWaiter(execution.id);
259 const toolInvoker = withExecutionInvocationContext({
260 executionId: execution.id,
261 toolInvoker: environment.toolInvoker,
262 });
232 263
233 yield* Effect.sync(() => {264 yield* Effect.sync(() => {
234 Effect.runFork(265 Effect.runFork(
235 environment.executor.execute(code, environment.toolInvoker).pipe(266 environment.executor.execute(code, toolInvoker).pipe(
236 Effect.flatMap((outcome) => {267 Effect.flatMap((outcome) => {
237 const completedAt = Date.now();268 const completedAt = Date.now();
238 return mapStorageError(269 return mapStorageError(
packages/control-plane/src/runtime/executor-tools.ts+112−0
@@ -0,0 +1,112 @@
1import {
2 type ElicitationResponse,
3 toTool,
4 type ToolMap,
5 type ToolPath,
6} from "@executor-v3/codemode-core";
7import {
8 ExecutionIdSchema,
9 ExecutionInteractionIdSchema,
10 type Source,
11 type WorkspaceId,
12} from "#schema";
13import * as Effect from "effect/Effect";
14import * as Schema from "effect/Schema";
15
16import { type RuntimeSourceAuthService } from "./source-auth-service";
17
18const ExecutorSourcesAddInputSchema = Schema.standardSchemaV1(
19 Schema.Struct({
20 endpoint: Schema.String,
21 name: Schema.optional(Schema.NullOr(Schema.String)),
22 namespace: Schema.optional(Schema.NullOr(Schema.String)),
23 }),
24);
25
26const ExecutorSourcesAddOutputSchema = Schema.standardSchemaV1(Schema.Unknown);
27
28const toExecutionId = (value: unknown) => {
29 if (typeof value !== "string" || value.trim().length === 0) {
30 throw new Error("Missing execution run id for executor.sources.add");
31 }
32
33 return ExecutionIdSchema.make(value);
34};
35
36const asToolPath = (value: string): ToolPath => value as ToolPath;
37
38export const createExecutorToolMap = (input: {
39 workspaceId: WorkspaceId;
40 sourceAuthService: RuntimeSourceAuthService;
41}): ToolMap => ({
42 "executor.sources.add": toTool({
43 tool: {
44 description: "Add an MCP source to the current workspace, starting OAuth if the source requires it",
45 inputSchema: ExecutorSourcesAddInputSchema,
46 outputSchema: ExecutorSourcesAddOutputSchema,
47 execute: async (
48 args: {
49 endpoint: string;
50 name?: string | null;
51 namespace?: string | null;
52 },
53 context,
54 ): Promise<Source> => {
55 const executionId = toExecutionId(context?.invocation?.runId);
56 const interactionId = ExecutionInteractionIdSchema.make(
57 `executor.sources.add:${crypto.randomUUID()}`,
58 );
59 const result = await Effect.runPromise(
60 input.sourceAuthService.addExecutorMcpSource({
61 workspaceId: input.workspaceId,
62 executionId,
63 interactionId,
64 endpoint: args.endpoint,
65 name: args.name ?? null,
66 namespace: args.namespace ?? null,
67 }),
68 );
69
70 if (result.kind === "connected") {
71 return result.source;
72 }
73
74 if (!context?.onElicitation) {
75 throw new Error("executor.sources.add requires an elicitation-capable host");
76 }
77
78 const response: ElicitationResponse = await Effect.runPromise(
79 context.onElicitation({
80 interactionId,
81 path: context.path ?? asToolPath("executor.sources.add"),
82 sourceKey: context.sourceKey,
83 args,
84 metadata: context.metadata,
85 context: context.invocation,
86 elicitation: {
87 mode: "url",
88 message: `Open the provider sign-in page to connect ${result.source.name}`,
89 url: result.authorizationUrl,
90 elicitationId: result.sessionId,
91 },
92 }),
93 );
94
95 if (response.action !== "accept") {
96 throw new Error(`Source add was not completed for ${result.source.id}`);
97 }
98
99 return await Effect.runPromise(
100 input.sourceAuthService.getSourceById({
101 workspaceId: input.workspaceId,
102 sourceId: result.source.id,
103 }),
104 );
105 },
106 },
107 metadata: {
108 sourceKey: "executor",
109 interaction: "auto",
110 },
111 }),
112});
packages/control-plane/src/runtime/index.ts+15−1
@@ -27,8 +27,11 @@ import { · 26 unmodified lines
27} from "./live-execution";27} from "./live-execution";
28import {28import {
29 makeWorkspaceExecutionEnvironmentResolver,29 makeWorkspaceExecutionEnvironmentResolver,
30 type ResolveSecretMaterial,
31} from "./workspace-execution-environment";30} from "./workspace-execution-environment";
31import {
32 makeRuntimeSourceAuthService,
33 type ResolveSecretMaterial,
34} from "./source-auth-service";
32import { makeRuntimeControlPlaneService } from "./services";35import { makeRuntimeControlPlaneService } from "./services";
33 36
34export {37export {
@@ -40,6 +43,7 @@ export { · 5 unmodified lines
40export * from "./execution-state";43export * from "./execution-state";
41export * from "./live-execution";44export * from "./live-execution";
42export * from "./local-installation";45export * from "./local-installation";
46export * from "./source-auth-service";
43export * from "./workspace-execution-environment";47export * from "./workspace-execution-environment";
44 48
45export type RuntimeControlPlaneInput = {49export type RuntimeControlPlaneInput = {
@@ -47,6 +51,7 @@ export type RuntimeControlPlaneInput = { · 1 unmodified line
47 actorResolver?: ControlPlaneActorResolverShape;51 actorResolver?: ControlPlaneActorResolverShape;
48 executionResolver?: ResolveExecutionEnvironment;52 executionResolver?: ResolveExecutionEnvironment;
49 resolveSecretMaterial?: ResolveSecretMaterial;53 resolveSecretMaterial?: ResolveSecretMaterial;
54 getLocalServerBaseUrl?: () => string | undefined;
50};55};
51 56
52export const makeRuntimeControlPlane = (57export const makeRuntimeControlPlane = (
@@ -56,15 +61,22 @@ export const makeRuntimeControlPlane = ( · 3 unmodified lines
56 actorResolver: ControlPlaneActorResolverShape;61 actorResolver: ControlPlaneActorResolverShape;
57} => {62} => {
58 const liveExecutionManager = makeLiveExecutionManager();63 const liveExecutionManager = makeLiveExecutionManager();
64 const sourceAuthService = makeRuntimeSourceAuthService({
65 rows: input.persistence.rows,
66 liveExecutionManager,
67 getLocalServerBaseUrl: input.getLocalServerBaseUrl,
68 });
59 const executionResolver =69 const executionResolver =
60 input.executionResolver70 input.executionResolver
61 ?? makeWorkspaceExecutionEnvironmentResolver({71 ?? makeWorkspaceExecutionEnvironmentResolver({
62 rows: input.persistence.rows,72 rows: input.persistence.rows,
63 resolveSecretMaterial: input.resolveSecretMaterial,73 resolveSecretMaterial: input.resolveSecretMaterial,
74 sourceAuthService,
64 });75 });
65 const service = makeRuntimeControlPlaneService(input.persistence.rows, {76 const service = makeRuntimeControlPlaneService(input.persistence.rows, {
66 executionResolver,77 executionResolver,
67 liveExecutionManager,78 liveExecutionManager,
79 sourceAuthService,
68 });80 });
69 const actorResolver = input.actorResolver ?? makeHeaderActorResolver(input.persistence.rows);81 const actorResolver = input.actorResolver ?? makeHeaderActorResolver(input.persistence.rows);
70 82
@@ -86,6 +98,7 @@ export type CreateSqlControlPlaneRuntimeOptions = CreateSqlRuntimeOptions & { · 15 unmodified lines
86 actorResolver?: ControlPlaneActorResolverShape;98 actorResolver?: ControlPlaneActorResolverShape;
87 executionResolver?: ResolveExecutionEnvironment;99 executionResolver?: ResolveExecutionEnvironment;
88 resolveSecretMaterial?: ResolveSecretMaterial;100 resolveSecretMaterial?: ResolveSecretMaterial;
101 getLocalServerBaseUrl?: () => string | undefined;
89};102};
90 103
91export const makeSqlControlPlaneRuntime = (104export const makeSqlControlPlaneRuntime = (
@@ -98,6 +111,7 @@ export const makeSqlControlPlaneRuntime = ( · 6 unmodified lines
98 actorResolver: options.actorResolver,111 actorResolver: options.actorResolver,
99 executionResolver: options.executionResolver,112 executionResolver: options.executionResolver,
100 resolveSecretMaterial: options.resolveSecretMaterial,113 resolveSecretMaterial: options.resolveSecretMaterial,
114 getLocalServerBaseUrl: options.getLocalServerBaseUrl,
101 });115 });
102 const localInstallation = yield* getOrProvisionLocalInstallation(persistence.rows).pipe(116 const localInstallation = yield* getOrProvisionLocalInstallation(persistence.rows).pipe(
103 Effect.mapError((cause) =>117 Effect.mapError((cause) =>
packages/control-plane/src/runtime/services.ts+28−2
@@ -25,6 +25,7 @@ import { type ResolveExecutionEnvironment } from "./execution-state"; · 24 unmodified lines
25import { type LiveExecutionManager } from "./live-execution";25import { type LiveExecutionManager } from "./live-execution";
26import { makeRuntimeExecutionsService } from "./execution-service";26import { makeRuntimeExecutionsService } from "./execution-service";
27import { loadLocalInstallation } from "./local-installation";27import { loadLocalInstallation } from "./local-installation";
28import { type RuntimeSourceAuthService } from "./source-auth-service";
28import {29import {
29 createSourceFromPayload,30 createSourceFromPayload,
30 projectSourceFromStorage,31 projectSourceFromStorage,
@@ -982,7 +983,8 @@ export const makeRuntimePoliciesService = ( · 951 unmodified lines
982 983
983export const makeRuntimeLocalService = (984export const makeRuntimeLocalService = (
984 rows: SqlControlPlaneRows,985 rows: SqlControlPlaneRows,
985): Pick<ControlPlaneServiceShape, "getLocalInstallation"> => ({986 sourceAuthService?: RuntimeSourceAuthService,
987): Pick<ControlPlaneServiceShape, "getLocalInstallation" | "completeSourceAuthCallback"> => ({
986 getLocalInstallation: () =>988 getLocalInstallation: () =>
987 Effect.gen(function* () {989 Effect.gen(function* () {
988 const installation = yield* loadLocalInstallation(rows).pipe(990 const installation = yield* loadLocalInstallation(rows).pipe(
@@ -1009,6 +1011,29 @@ export const makeRuntimeLocalService = ( · 20 unmodified lines
1009 1011
1010 return installation;1012 return installation;
1011 }),1013 }),
1014
1015 completeSourceAuthCallback: (input) =>
1016 Effect.gen(function* () {
1017 if (!sourceAuthService) {
1018 return yield* Effect.fail(
1019 badRequest(
1020 "local.oauth.callback",
1021 "Source auth callback handling is unavailable",
1022 "Runtime source auth service is not configured",
1023 ),
1024 );
1025 }
1026
1027 return yield* sourceAuthService.completeSourceAuthCallback(input).pipe(
1028 Effect.mapError((error) =>
1029 new ControlPlaneStorageError({
1030 operation: "local.oauth.callback",
1031 message: error instanceof Error ? error.message : String(error),
1032 details: "Failed completing source auth callback",
1033 }),
1034 ),
1035 );
1036 }),
1012 });1037 });
1013 1038
1014export const makeRuntimeControlPlaneService = (1039export const makeRuntimeControlPlaneService = (
@@ -1016,9 +1041,10 @@ export const makeRuntimeControlPlaneService = ( · 1 unmodified line
1016 options: {1041 options: {
1017 executionResolver?: ResolveExecutionEnvironment;1042 executionResolver?: ResolveExecutionEnvironment;
1018 liveExecutionManager?: LiveExecutionManager;1043 liveExecutionManager?: LiveExecutionManager;
1044 sourceAuthService?: RuntimeSourceAuthService;
1019 } = {},1045 } = {},
1020): ControlPlaneServiceShape => ({1046): ControlPlaneServiceShape => ({
1021 ...makeRuntimeLocalService(rows),1047 ...makeRuntimeLocalService(rows, options.sourceAuthService),
1022 ...makeRuntimeOrganizationsService(rows),1048 ...makeRuntimeOrganizationsService(rows),
1023 ...makeRuntimeMembershipsService(rows),1049 ...makeRuntimeMembershipsService(rows),
1024 ...makeRuntimeWorkspacesService(rows),1050 ...makeRuntimeWorkspacesService(rows),
packages/control-plane/src/runtime/source-auth-service.ts+769−0
@@ -0,0 +1,769 @@
1import {
2 auth,
3 type OAuthClientProvider,
4 type OAuthDiscoveryState,
5} from "@modelcontextprotocol/sdk/client/auth.js";
6import type { OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js";
7import {
8 createSdkMcpConnector,
9 discoverMcpToolsFromConnector,
10} from "@executor-v3/codemode-mcp";
11import { type SqlControlPlaneRows } from "#persistence";
12import {
13 ExecutionIdSchema,
14 SecretMaterialIdSchema,
15 Source,
16 SourceAuthSession,
17 SourceAuthSessionIdSchema,
18 SourceIdSchema,
19 SourceSchema,
20 type SecretRef,
21 type WorkspaceId,
22} from "#schema";
23import * as Effect from "effect/Effect";
24import * as Option from "effect/Option";
25import * as Schema from "effect/Schema";
26
27import type { LiveExecutionManager } from "./live-execution";
28import {
29 createSourceFromPayload,
30 projectSourceFromStorage,
31 projectSourcesFromStorage,
32 splitSourceForStorage,
33 updateSourceFromPayload,
34} from "./source-definitions";
35
36export const CONTROL_PLANE_SECRET_PROVIDER_ID = "control-plane";
37const OAUTH_CALLBACK_PATH = "/v1/local/oauth/callback";
38
39const trimOrNull = (value: string | null | undefined): string | null => {
40 if (value === null || value === undefined) {
41 return null;
42 }
43
44 const trimmed = value.trim();
45 return trimmed.length > 0 ? trimmed : null;
46};
47
48const defaultSourceNameFromEndpoint = (endpoint: string): string => {
49 const url = new URL(endpoint);
50 return url.hostname;
51};
52
53const defaultNamespaceFromName = (name: string): string => {
54 const normalized = name
55 .trim()
56 .toLowerCase()
57 .replace(/[^a-z0-9]+/g, ".")
58 .replace(/^\.+|\.+$/g, "");
59
60 return normalized.length > 0 ? normalized : "source";
61};
62
63const serializeJson = (value: unknown): string | null => {
64 if (value === undefined || value === null) {
65 return null;
66 }
67
68 return JSON.stringify(value);
69};
70
71const decodeJson = <A>(input: {
72 value: string | null;
73 fallback: A;
74}): A => {
75 if (input.value === null) {
76 return input.fallback;
77 }
78
79 try {
80 return JSON.parse(input.value) as A;
81 } catch {
82 return input.fallback;
83 }
84};
85
86const resolveRedirectUrl = (baseUrl: string): string =>
87 new URL(OAUTH_CALLBACK_PATH, baseUrl).toString();
88
89const normalizeEndpoint = (endpoint: string): string => {
90 const url = new URL(endpoint.trim());
91 return url.toString();
92};
93
94const loadSourcesInWorkspace = (rows: SqlControlPlaneRows, workspaceId: WorkspaceId) =>
95 Effect.gen(function* () {
96 const sourceRecords = yield* rows.sources.listByWorkspaceId(workspaceId);
97 const credentialBindings = yield* rows.sourceCredentialBindings.listByWorkspaceId(workspaceId);
98
99 return yield* projectSourcesFromStorage({
100 sourceRecords,
101 credentialBindings,
102 });
103 });
104
105const loadSourceById = (rows: SqlControlPlaneRows, input: {
106 workspaceId: WorkspaceId;
107 sourceId: Source["id"];
108}) =>
109 Effect.gen(function* () {
110 const sourceRecord = yield* rows.sources.getByWorkspaceAndId(
111 input.workspaceId,
112 input.sourceId,
113 );
114
115 if (Option.isNone(sourceRecord)) {
116 return yield* Effect.fail(
117 new Error(`Source not found: workspaceId=${input.workspaceId} sourceId=${input.sourceId}`),
118 );
119 }
120
121 const credentialBinding = yield* rows.sourceCredentialBindings.getByWorkspaceAndSourceId(
122 input.workspaceId,
123 input.sourceId,
124 );
125
126 return yield* projectSourceFromStorage({
127 sourceRecord: sourceRecord.value,
128 credentialBinding: Option.isSome(credentialBinding) ? credentialBinding.value : null,
129 });
130 });
131
132const persistSource = (rows: SqlControlPlaneRows, source: Source) =>
133 Effect.gen(function* () {
134 const { sourceRecord, credentialBinding } = splitSourceForStorage({ source });
135 const existing = yield* rows.sources.getByWorkspaceAndId(source.workspaceId, source.id);
136
137 if (Option.isNone(existing)) {
138 yield* rows.sources.insert(sourceRecord);
139 } else {
140 const { id: _id, workspaceId: _workspaceId, createdAt: _createdAt, ...patch } = sourceRecord;
141 yield* rows.sources.update(source.workspaceId, source.id, patch);
142 }
143
144 if (credentialBinding === null) {
145 yield* rows.sourceCredentialBindings.removeByWorkspaceAndSourceId(
146 source.workspaceId,
147 source.id,
148 );
149 } else {
150 yield* rows.sourceCredentialBindings.upsert(credentialBinding);
151 }
152
153 return source;
154 });
155
156const probeMcpSourceWithoutAuth = (source: Source) =>
157 Effect.gen(function* () {
158 if (source.kind !== "mcp") {
159 return yield* Effect.fail(new Error(`Expected MCP source, received ${source.kind}`));
160 }
161
162 const connector = yield* Effect.try({
163 try: () =>
164 createSdkMcpConnector({
165 endpoint: source.endpoint,
166 transport: source.transport ?? undefined,
167 queryParams: source.queryParams ?? undefined,
168 headers: source.headers ?? undefined,
169 }),
170 catch: (cause) =>
171 cause instanceof Error ? cause : new Error(String(cause)),
172 });
173
174 return yield* discoverMcpToolsFromConnector({
175 connect: connector,
176 namespace: source.namespace ?? defaultNamespaceFromName(source.name),
177 sourceKey: source.id,
178 });
179 });
180
181const makeAddSourceClientMetadata = (redirectUrl: string) => ({
182 redirect_uris: [redirectUrl],
183 grant_types: ["authorization_code", "refresh_token"],
184 response_types: ["code"],
185 token_endpoint_auth_method: "none",
186 client_name: "Executor Local",
187});
188
189type PendingOAuthStart = {
190 authorizationUrl: string;
191 codeVerifier: string;
192 resourceMetadataUrl: string | null;
193 authorizationServerUrl: string | null;
194 resourceMetadataJson: string | null;
195 authorizationServerMetadataJson: string | null;
196 clientInformationJson: string | null;
197};
198
199const startOAuthAuthorization = (input: {
200 endpoint: string;
201 redirectUrl: string;
202 state: string;
203}): Effect.Effect<PendingOAuthStart, Error, never> =>
204 Effect.tryPromise({
205 try: async () => {
206 const captured: {
207 authorizationUrl?: URL;
208 codeVerifier?: string;
209 discoveryState?: OAuthDiscoveryState;
210 clientInformation?: unknown;
211 } = {};
212
213 const provider: OAuthClientProvider = {
214 get redirectUrl() {
215 return input.redirectUrl;
216 },
217 get clientMetadata() {
218 return makeAddSourceClientMetadata(input.redirectUrl);
219 },
220 state: () => input.state,
221 clientInformation: () => captured.clientInformation as any,
222 saveClientInformation: (clientInformation) => {
223 captured.clientInformation = clientInformation;
224 },
225 tokens: () => undefined,
226 saveTokens: () => undefined,
227 redirectToAuthorization: (authorizationUrl) => {
228 captured.authorizationUrl = authorizationUrl;
229 },
230 saveCodeVerifier: (codeVerifier) => {
231 captured.codeVerifier = codeVerifier;
232 },
233 codeVerifier: () => {
234 if (!captured.codeVerifier) {
235 throw new Error("OAuth code verifier was not captured");
236 }
237
238 return captured.codeVerifier;
239 },
240 saveDiscoveryState: (state) => {
241 captured.discoveryState = state;
242 },
243 discoveryState: () => captured.discoveryState,
244 };
245
246 const result = await auth(provider, {
247 serverUrl: input.endpoint,
248 });
249
250 if (result !== "REDIRECT" || !captured.authorizationUrl || !captured.codeVerifier) {
251 throw new Error("OAuth flow did not produce an authorization redirect");
252 }
253
254 return {
255 authorizationUrl: captured.authorizationUrl.toString(),
256 codeVerifier: captured.codeVerifier,
257 resourceMetadataUrl: captured.discoveryState?.resourceMetadataUrl ?? null,
258 authorizationServerUrl: captured.discoveryState?.authorizationServerUrl ?? null,
259 resourceMetadataJson: serializeJson(captured.discoveryState?.resourceMetadata),
260 authorizationServerMetadataJson: serializeJson(
261 captured.discoveryState?.authorizationServerMetadata,
262 ),
263 clientInformationJson: serializeJson(captured.clientInformation),
264 } satisfies PendingOAuthStart;
265 },
266 catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))),
267 });
268
269type ExchangedTokens = {
270 tokens: OAuthTokens;
271 clientInformationJson: string | null;
272 resourceMetadataUrl: string | null;
273 authorizationServerUrl: string | null;
274 resourceMetadataJson: string | null;
275 authorizationServerMetadataJson: string | null;
276};
277
278const exchangeOAuthAuthorizationCode = (input: {
279 session: SourceAuthSession;
280 code: string;
281}): Effect.Effect<ExchangedTokens, Error, never> =>
282 Effect.tryPromise({
283 try: async () => {
284 const captured: {
285 tokens?: OAuthTokens;
286 discoveryState?: OAuthDiscoveryState;
287 clientInformation?: unknown;
288 } = {
289 discoveryState: {
290 authorizationServerUrl: input.session.authorizationServerUrl ?? new URL("/", input.session.endpoint).toString(),
291 resourceMetadataUrl: input.session.resourceMetadataUrl ?? undefined,
292 resourceMetadata: decodeJson({
293 value: input.session.resourceMetadataJson,
294 fallback: undefined,
295 }),
296 authorizationServerMetadata: decodeJson({
297 value: input.session.authorizationServerMetadataJson,
298 fallback: undefined,
299 }),
300 },
301 clientInformation: decodeJson({
302 value: input.session.clientInformationJson,
303 fallback: undefined,
304 }),
305 };
306
307 const provider: OAuthClientProvider = {
308 get redirectUrl() {
309 return input.session.redirectUri;
310 },
311 get clientMetadata() {
312 return makeAddSourceClientMetadata(input.session.redirectUri);
313 },
314 clientInformation: () => captured.clientInformation as any,
315 saveClientInformation: (clientInformation) => {
316 captured.clientInformation = clientInformation;
317 },
318 tokens: () => undefined,
319 saveTokens: (tokens) => {
320 captured.tokens = tokens;
321 },
322 redirectToAuthorization: () => {
323 throw new Error("Unexpected redirect during OAuth callback completion");
324 },
325 saveCodeVerifier: () => undefined,
326 codeVerifier: () => {
327 if (!input.session.codeVerifier) {
328 throw new Error("OAuth session is missing the PKCE code verifier");
329 }
330
331 return input.session.codeVerifier;
332 },
333 saveDiscoveryState: (state) => {
334 captured.discoveryState = state;
335 },
336 discoveryState: () => captured.discoveryState,
337 };
338
339 const result = await auth(provider, {
340 serverUrl: input.session.endpoint,
341 authorizationCode: input.code,
342 });
343
344 if (result !== "AUTHORIZED" || !captured.tokens) {
345 throw new Error("OAuth callback did not complete authorization");
346 }
347
348 return {
349 tokens: captured.tokens,
350 clientInformationJson: serializeJson(captured.clientInformation),
351 resourceMetadataUrl: captured.discoveryState?.resourceMetadataUrl ?? null,
352 authorizationServerUrl: captured.discoveryState?.authorizationServerUrl ?? null,
353 resourceMetadataJson: serializeJson(captured.discoveryState?.resourceMetadata),
354 authorizationServerMetadataJson: serializeJson(
355 captured.discoveryState?.authorizationServerMetadata,
356 ),
357 } satisfies ExchangedTokens;
358 },
359 catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))),
360 });
361
362const completeLiveInteraction = (input: {
363 liveExecutionManager: LiveExecutionManager;
364 session: SourceAuthSession;
365 response: {
366 action: "accept" | "cancel";
367 reason?: string;
368 };
369}) =>
370 Effect.gen(function* () {
371 if (input.session.executionId === null) {
372 return;
373 }
374
375 yield* input.liveExecutionManager.resolveInteraction({
376 executionId: input.session.executionId,
377 response:
378 input.response.action === "accept"
379 ? { action: "accept" }
380 : {
381 action: "cancel",
382 ...(input.response.reason
383 ? {
384 content: {
385 reason: input.response.reason,
386 },
387 }
388 : {}),
389 },
390 });
391 });
392
393const updateSourceStatus = (rows: SqlControlPlaneRows, source: Source, input: {
394 status: Source["status"];
395 lastError?: string | null;
396 auth?: Source["auth"];
397}) =>
398 persistSource(rows, {
399 ...source,
400 status: input.status,
401 lastError: input.lastError ?? null,
402 auth: input.auth ?? source.auth,
403 updatedAt: Date.now(),
404 });
405
406const upsertSecretMaterial = (rows: SqlControlPlaneRows, input: {
407 purpose: "oauth_access_token" | "oauth_refresh_token";
408 value: string;
409}) =>
410 Effect.gen(function* () {
411 const now = Date.now();
412 const materialId = SecretMaterialIdSchema.make(`sec_${crypto.randomUUID()}`);
413 yield* rows.secretMaterials.upsert({
414 id: materialId,
415 purpose: input.purpose,
416 value: input.value,
417 createdAt: now,
418 updatedAt: now,
419 });
420
421 return materialId;
422 });
423
424export type ExecutorSourceAddResult =
425 | {
426 kind: "connected";
427 source: Source;
428 }
429 | {
430 kind: "oauth_required";
431 source: Source;
432 sessionId: SourceAuthSession["id"];
433 authorizationUrl: string;
434 };
435
436export type RuntimeSourceAuthService = {
437 getSourceById: (input: {
438 workspaceId: WorkspaceId;
439 sourceId: Source["id"];
440 }) => Effect.Effect<Source, Error, never>;
441 addExecutorMcpSource: (input: {
442 workspaceId: WorkspaceId;
443 executionId: SourceAuthSession["executionId"];
444 interactionId: SourceAuthSession["interactionId"];
445 endpoint: string;
446 name?: string | null;
447 namespace?: string | null;
448 }) => Effect.Effect<ExecutorSourceAddResult, Error, never>;
449 completeSourceAuthCallback: (input: {
450 state: string;
451 code?: string | null;
452 error?: string | null;
453 errorDescription?: string | null;
454 }) => Effect.Effect<Source, Error, never>;
455};
456
457export type ResolveSecretMaterial = (ref: SecretRef) => Effect.Effect<string, Error, never>;
458
459export const makeDbBackedSecretMaterialResolver = (input: {
460 rows: SqlControlPlaneRows;
461 fallback?: ResolveSecretMaterial;
462}): ResolveSecretMaterial =>
463 (ref) =>
464 Effect.gen(function* () {
465 if (ref.providerId === CONTROL_PLANE_SECRET_PROVIDER_ID) {
466 const materialId = SecretMaterialIdSchema.make(ref.handle);
467 const stored = yield* input.rows.secretMaterials.getById(materialId);
468 if (Option.isNone(stored)) {
469 return yield* Effect.fail(
470 new Error(`Secret material not found: ${ref.handle}`),
471 );
472 }
473
474 return stored.value.value;
475 }
476
477 if (input.fallback) {
478 return yield* input.fallback(ref);
479 }
480
481 return yield* Effect.fail(
482 new Error(`Unsupported secret provider ${ref.providerId}`),
483 );
484 });
485
486export const makeRuntimeSourceAuthService = (input: {
487 rows: SqlControlPlaneRows;
488 liveExecutionManager: LiveExecutionManager;
489 getLocalServerBaseUrl?: () => string | undefined;
490}): RuntimeSourceAuthService => ({
491 getSourceById: ({ workspaceId, sourceId }) =>
492 loadSourceById(input.rows, {
493 workspaceId,
494 sourceId,
495 }),
496
497 addExecutorMcpSource: ({
498 workspaceId,
499 executionId,
500 interactionId,
501 endpoint,
502 name,
503 namespace,
504 }) =>
505 Effect.gen(function* () {
506 const normalizedEndpoint = normalizeEndpoint(endpoint);
507 const existingSources = yield* loadSourcesInWorkspace(input.rows, workspaceId);
508 const existing = existingSources.find(
509 (source) => source.kind === "mcp" && normalizeEndpoint(source.endpoint) === normalizedEndpoint,
510 );
511
512 const chosenName =
513 trimOrNull(name)
514 ?? existing?.name
515 ?? defaultSourceNameFromEndpoint(normalizedEndpoint);
516 const chosenNamespace =
517 trimOrNull(namespace)
518 ?? existing?.namespace
519 ?? defaultNamespaceFromName(chosenName);
520 const now = Date.now();
521
522 const draftSource = existing
523 ? yield* updateSourceFromPayload({
524 source: existing,
525 payload: {
526 name: chosenName,
527 endpoint: normalizedEndpoint,
528 namespace: chosenNamespace,
529 kind: "mcp",
530 status: "probing",
531 enabled: true,
532 transport: existing.transport ?? "auto",
533 auth: { kind: "none" },
534 lastError: null,
535 },
536 now,
537 })
538 : yield* createSourceFromPayload({
539 workspaceId,
540 sourceId: SourceIdSchema.make(`src_${crypto.randomUUID()}`),
541 payload: {
542 name: chosenName,
543 kind: "mcp",
544 endpoint: normalizedEndpoint,
545 namespace: chosenNamespace,
546 status: "probing",
547 enabled: true,
548 transport: "auto",
549 auth: { kind: "none" },
550 },
551 now,
552 });
553
554 const persistedDraft = yield* persistSource(input.rows, draftSource);
555
556 const discovered = yield* Effect.either(
557 probeMcpSourceWithoutAuth(persistedDraft),
558 );
559
560 if (discovered._tag === "Right") {
561 const connected = yield* updateSourceStatus(input.rows, persistedDraft, {
562 status: "connected",
563 lastError: null,
564 auth: { kind: "none" },
565 });
566
567 return {
568 kind: "connected",
569 source: connected,
570 } satisfies ExecutorSourceAddResult;
571 }
572
573 const localServerBaseUrl = input.getLocalServerBaseUrl?.();
574 if (!localServerBaseUrl) {
575 return yield* Effect.fail(
576 new Error("Local executor server base URL is unavailable for OAuth callback handling"),
577 );
578 }
579
580 const sessionId = SourceAuthSessionIdSchema.make(`src_auth_${crypto.randomUUID()}`);
581 const state = crypto.randomUUID();
582 const redirectUrl = resolveRedirectUrl(localServerBaseUrl);
583 const oauthStart = yield* startOAuthAuthorization({
584 endpoint: normalizedEndpoint,
585 redirectUrl,
586 state,
587 });
588
589 const authRequiredSource = yield* updateSourceStatus(input.rows, persistedDraft, {
590 status: "auth_required",
591 lastError: null,
592 });
593
594 const sessionNow = Date.now();
595 yield* input.rows.sourceAuthSessions.upsert({
596 id: sessionId,
597 workspaceId,
598 sourceId: authRequiredSource.id,
599 executionId,
600 interactionId,
601 strategy: "oauth2_authorization_code",
602 status: "pending",
603 endpoint: normalizedEndpoint,
604 state,
605 redirectUri: redirectUrl,
606 scope: null,
607 resourceMetadataUrl: oauthStart.resourceMetadataUrl,
608 authorizationServerUrl: oauthStart.authorizationServerUrl,
609 resourceMetadataJson: oauthStart.resourceMetadataJson,
610 authorizationServerMetadataJson: oauthStart.authorizationServerMetadataJson,
611 clientInformationJson: oauthStart.clientInformationJson,
612 codeVerifier: oauthStart.codeVerifier,
613 authorizationUrl: oauthStart.authorizationUrl,
614 errorText: null,
615 completedAt: null,
616 createdAt: sessionNow,
617 updatedAt: sessionNow,
618 });
619
620 return {
621 kind: "oauth_required",
622 source: authRequiredSource,
623 sessionId,
624 authorizationUrl: oauthStart.authorizationUrl,
625 } satisfies ExecutorSourceAddResult;
626 }),
627
628 completeSourceAuthCallback: ({ state, code, error, errorDescription }) =>
629 Effect.gen(function* () {
630 const sessionOption = yield* input.rows.sourceAuthSessions.getByState(state);
631 if (Option.isNone(sessionOption)) {
632 return yield* Effect.fail(new Error(`Source auth session not found for state ${state}`));
633 }
634
635 const session = sessionOption.value;
636 const source = yield* loadSourceById(input.rows, {
637 workspaceId: session.workspaceId,
638 sourceId: session.sourceId,
639 });
640
641 if (session.status === "completed") {
642 return source;
643 }
644
645 if (session.status !== "pending") {
646 return yield* Effect.fail(
647 new Error(`Source auth session ${session.id} is not pending`),
648 );
649 }
650
651 if (trimOrNull(error) !== null) {
652 const reason = trimOrNull(errorDescription) ?? trimOrNull(error) ?? "OAuth authorization failed";
653 const failedAt = Date.now();
654
655 yield* input.rows.sourceAuthSessions.update(session.id, {
656 status: "failed",
657 errorText: reason,
658 completedAt: failedAt,
659 updatedAt: failedAt,
660 });
661 yield* updateSourceStatus(input.rows, source, {
662 status: "error",
663 lastError: reason,
664 });
665 yield* completeLiveInteraction({
666 liveExecutionManager: input.liveExecutionManager,
667 session,
668 response: {
669 action: "cancel",
670 reason,
671 },
672 });
673
674 return yield* Effect.fail(new Error(reason));
675 }
676
677 const authorizationCode = trimOrNull(code);
678 if (authorizationCode === null) {
679 return yield* Effect.fail(new Error("Missing OAuth authorization code"));
680 }
681
682 const exchanged = yield* exchangeOAuthAuthorizationCode({
683 session,
684 code: authorizationCode,
685 });
686
687 const accessTokenId = yield* upsertSecretMaterial(input.rows, {
688 purpose: "oauth_access_token",
689 value: exchanged.tokens.access_token,
690 });
691 const refreshTokenId = exchanged.tokens.refresh_token
692 ? yield* upsertSecretMaterial(input.rows, {
693 purpose: "oauth_refresh_token",
694 value: exchanged.tokens.refresh_token,
695 })
696 : null;
697
698 const now = Date.now();
699 yield* input.rows.sourceCredentialBindings.upsert({
700 workspaceId: session.workspaceId,
701 sourceId: session.sourceId,
702 tokenProviderId: CONTROL_PLANE_SECRET_PROVIDER_ID,
703 tokenHandle: accessTokenId,
704 refreshTokenProviderId:
705 refreshTokenId === null ? null : CONTROL_PLANE_SECRET_PROVIDER_ID,
706 refreshTokenHandle: refreshTokenId,
707 createdAt: now,
708 updatedAt: now,
709 });
710
711 const connectedSource = yield* updateSourceStatus(input.rows, source, {
712 status: "connected",
713 lastError: null,
714 auth: {
715 kind: "oauth2",
716 headerName: "Authorization",
717 prefix: "Bearer ",
718 accessToken: {
719 providerId: CONTROL_PLANE_SECRET_PROVIDER_ID,
720 handle: accessTokenId,
721 },
722 refreshToken:
723 refreshTokenId === null
724 ? null
725 : {
726 providerId: CONTROL_PLANE_SECRET_PROVIDER_ID,
727 handle: refreshTokenId,
728 },
729 },
730 });
731
732 yield* input.rows.sourceAuthSessions.update(session.id, {
733 status: "completed",
734 errorText: null,
735 completedAt: now,
736 updatedAt: now,
737 resourceMetadataUrl: exchanged.resourceMetadataUrl,
738 authorizationServerUrl: exchanged.authorizationServerUrl,
739 resourceMetadataJson: exchanged.resourceMetadataJson,
740 authorizationServerMetadataJson: exchanged.authorizationServerMetadataJson,
741 clientInformationJson: exchanged.clientInformationJson,
742 });
743
744 yield* completeLiveInteraction({
745 liveExecutionManager: input.liveExecutionManager,
746 session,
747 response: {
748 action: "accept",
749 },
750 });
751
752 return connectedSource;
753 }),
754});
755
756export const ExecutorAddSourceResultSchema = Schema.Union(
757 Schema.Struct({
758 kind: Schema.Literal("connected"),
759 source: SourceSchema,
760 }),
761 Schema.Struct({
762 kind: Schema.Literal("oauth_required"),
763 source: SourceSchema,
764 sessionId: SourceAuthSessionIdSchema,
765 authorizationUrl: Schema.String,
766 }),
767);
768
769export type ExecutorAddSourceResult = typeof ExecutorAddSourceResultSchema.Type;
packages/control-plane/src/runtime/workspace-execution-environment.ts+22−8
@@ -16,7 +16,6 @@ import { · 15 unmodified lines
16import { makeInProcessExecutor } from "@executor-v3/runtime-local-inproc";16import { makeInProcessExecutor } from "@executor-v3/runtime-local-inproc";
17import { type SqlControlPlaneRows } from "#persistence";17import { type SqlControlPlaneRows } from "#persistence";
18import type {18import type {
19 SecretRef,
20 Source,19 Source,
21} from "#schema";20} from "#schema";
22import * as Effect from "effect/Effect";21import * as Effect from "effect/Effect";
@@ -25,11 +24,13 @@ import type { · 2 unmodified lines
25 ExecutionEnvironment,24 ExecutionEnvironment,
26 ResolveExecutionEnvironment,25 ResolveExecutionEnvironment,
27} from "./execution-state";26} from "./execution-state";
27import { createExecutorToolMap } from "./executor-tools";
28import { projectSourcesFromStorage } from "./source-definitions";28import { projectSourcesFromStorage } from "./source-definitions";
29 29import {
30export type ResolveSecretMaterial = (30 makeDbBackedSecretMaterialResolver,
31 ref: SecretRef,31 type ResolveSecretMaterial,
32) => Effect.Effect<string, Error, never>;32 type RuntimeSourceAuthService,
33} from "./source-auth-service";
33 34
34export type ResolvedSourceAuthMaterial = {35export type ResolvedSourceAuthMaterial = {
35 headers: Readonly<Record<string, string>>;36 headers: Readonly<Record<string, string>>;
@@ -204,8 +205,14 @@ const loadSourceTools = (input: { · 168 unmodified lines
204export const makeWorkspaceExecutionEnvironmentResolver = (input: {205export const makeWorkspaceExecutionEnvironmentResolver = (input: {
205 rows: SqlControlPlaneRows;206 rows: SqlControlPlaneRows;
206 resolveSecretMaterial?: ResolveSecretMaterial;207 resolveSecretMaterial?: ResolveSecretMaterial;
208 sourceAuthService: RuntimeSourceAuthService;
207}): ResolveExecutionEnvironment => {209}): ResolveExecutionEnvironment => {
208 const resolveSecretMaterial = input.resolveSecretMaterial ?? makeEnvSecretMaterialResolver();210 const resolveSecretMaterial =
211 input.resolveSecretMaterial
212 ?? makeDbBackedSecretMaterialResolver({
213 rows: input.rows,
214 fallback: makeEnvSecretMaterialResolver(),
215 });
209 216
210 return ({ workspaceId, onElicitation }) =>217 return ({ workspaceId, onElicitation }) =>
211 Effect.gen(function* () {218 Effect.gen(function* () {
@@ -226,7 +233,9 @@ export const makeWorkspaceExecutionEnvironmentResolver = (input: { · 14 unmodified lines
226 sourceRecords,233 sourceRecords,
227 credentialBindings,234 credentialBindings,
228 });235 });
229 const enabledSources = sources.filter((source) => source.enabled);236 const enabledSources = sources.filter(
237 (source) => source.enabled && source.status === "connected",
238 );
230 239
231 const discoveredToolMaps = yield* Effect.forEach(240 const discoveredToolMaps = yield* Effect.forEach(
232 enabledSources,241 enabledSources,
@@ -244,9 +253,14 @@ export const makeWorkspaceExecutionEnvironmentResolver = (input: { · 11 unmodified lines
244 { concurrency: "unbounded" },253 { concurrency: "unbounded" },
245 );254 );
246 255
256 const executorTools = createExecutorToolMap({
257 workspaceId,
258 sourceAuthService: input.sourceAuthService,
259 });
260
247 const sourceTools = yield* Effect.try({261 const sourceTools = yield* Effect.try({
248 try: () =>262 try: () =>
249 mergeToolMaps(discoveredToolMaps, {263 mergeToolMaps([...discoveredToolMaps, executorTools], {
250 conflictMode: "throw",264 conflictMode: "throw",
251 }),265 }),
252 catch: (cause) =>266 catch: (cause) =>
packages/control-plane/src/schema/ids.ts+8−0
@@ -7,6 +7,12 @@ export const OrganizationMemberIdSchema = Schema.String.pipe( · 6 unmodified lines
7);7);
8export const WorkspaceIdSchema = Schema.String.pipe(Schema.brand("WorkspaceId"));8export const WorkspaceIdSchema = Schema.String.pipe(Schema.brand("WorkspaceId"));
9export const SourceIdSchema = Schema.String.pipe(Schema.brand("SourceId"));9export const SourceIdSchema = Schema.String.pipe(Schema.brand("SourceId"));
10export const SourceAuthSessionIdSchema = Schema.String.pipe(
11 Schema.brand("SourceAuthSessionId"),
12);
13export const SecretMaterialIdSchema = Schema.String.pipe(
14 Schema.brand("SecretMaterialId"),
15);
10export const PolicyIdSchema = Schema.String.pipe(Schema.brand("PolicyId"));16export const PolicyIdSchema = Schema.String.pipe(Schema.brand("PolicyId"));
11export const InstallationIdSchema = Schema.String.pipe(Schema.brand("InstallationId"));17export const InstallationIdSchema = Schema.String.pipe(Schema.brand("InstallationId"));
12export const ExecutionIdSchema = Schema.String.pipe(Schema.brand("ExecutionId"));18export const ExecutionIdSchema = Schema.String.pipe(Schema.brand("ExecutionId"));
@@ -19,6 +25,8 @@ export type OrganizationId = typeof OrganizationIdSchema.Type; · 6 unmodified lines
19export type OrganizationMemberId = typeof OrganizationMemberIdSchema.Type;25export type OrganizationMemberId = typeof OrganizationMemberIdSchema.Type;
20export type WorkspaceId = typeof WorkspaceIdSchema.Type;26export type WorkspaceId = typeof WorkspaceIdSchema.Type;
21export type SourceId = typeof SourceIdSchema.Type;27export type SourceId = typeof SourceIdSchema.Type;
28export type SourceAuthSessionId = typeof SourceAuthSessionIdSchema.Type;
29export type SecretMaterialId = typeof SecretMaterialIdSchema.Type;
22export type PolicyId = typeof PolicyIdSchema.Type;30export type PolicyId = typeof PolicyIdSchema.Type;
23export type InstallationId = typeof InstallationIdSchema.Type;31export type InstallationId = typeof InstallationIdSchema.Type;
24export type ExecutionId = typeof ExecutionIdSchema.Type;32export type ExecutionId = typeof ExecutionIdSchema.Type;
packages/control-plane/src/schema/index.ts+2−0
@@ -5,6 +5,8 @@ export * from "./ids"; · 4 unmodified lines
5export * from "./models/auth";5export * from "./models/auth";
6export * from "./models/workspace";6export * from "./models/workspace";
7export * from "./models/source";7export * from "./models/source";
8export * from "./models/secret-material";
9export * from "./models/source-auth-session";
8export * from "./models/policy";10export * from "./models/policy";
9export * from "./models/local-installation";11export * from "./models/local-installation";
10export * from "./models/execution";12export * from "./models/execution";
packages/control-plane/src/schema/models/secret-material.ts+28−0
@@ -0,0 +1,28 @@
1import { createSelectSchema } from "drizzle-orm/effect-schema";
2import { Schema } from "effect";
3
4import { secretMaterialsTable } from "../../persistence/schema";
5import { TimestampMsSchema } from "../common";
6import { SecretMaterialIdSchema } from "../ids";
7
8export const SecretMaterialPurposeSchema = Schema.Literal(
9 "auth_material",
10 "oauth_access_token",
11 "oauth_refresh_token",
12 "oauth_client_info",
13);
14
15const secretMaterialSchemaOverrides = {
16 id: SecretMaterialIdSchema,
17 purpose: SecretMaterialPurposeSchema,
18 createdAt: TimestampMsSchema,
19 updatedAt: TimestampMsSchema,
20} as const;
21
22export const SecretMaterialSchema = createSelectSchema(
23 secretMaterialsTable,
24 secretMaterialSchemaOverrides,
25);
26
27export type SecretMaterialPurpose = typeof SecretMaterialPurposeSchema.Type;
28export type SecretMaterial = typeof SecretMaterialSchema.Type;
packages/control-plane/src/schema/models/source-auth-session.ts+45−0
@@ -0,0 +1,45 @@
1import { createSelectSchema } from "drizzle-orm/effect-schema";
2import { Schema } from "effect";
3
4import { sourceAuthSessionsTable } from "../../persistence/schema";
5import { TimestampMsSchema } from "../common";
6import {
7 ExecutionIdSchema,
8 ExecutionInteractionIdSchema,
9 SourceAuthSessionIdSchema,
10 SourceIdSchema,
11 WorkspaceIdSchema,
12} from "../ids";
13
14export const SourceAuthSessionStrategySchema = Schema.Literal(
15 "oauth2_authorization_code",
16);
17
18export const SourceAuthSessionStatusSchema = Schema.Literal(
19 "pending",
20 "completed",
21 "failed",
22 "cancelled",
23);
24
25const sourceAuthSessionSchemaOverrides = {
26 id: SourceAuthSessionIdSchema,
27 workspaceId: WorkspaceIdSchema,
28 sourceId: SourceIdSchema,
29 executionId: Schema.NullOr(ExecutionIdSchema),
30 interactionId: Schema.NullOr(ExecutionInteractionIdSchema),
31 strategy: SourceAuthSessionStrategySchema,
32 status: SourceAuthSessionStatusSchema,
33 completedAt: Schema.NullOr(TimestampMsSchema),
34 createdAt: TimestampMsSchema,
35 updatedAt: TimestampMsSchema,
36} as const;
37
38export const SourceAuthSessionSchema = createSelectSchema(
39 sourceAuthSessionsTable,
40 sourceAuthSessionSchemaOverrides,
41);
42
43export type SourceAuthSessionStrategy = typeof SourceAuthSessionStrategySchema.Type;
44export type SourceAuthSessionStatus = typeof SourceAuthSessionStatusSchema.Type;
45export type SourceAuthSession = typeof SourceAuthSessionSchema.Type;