History

Remove SQL control-plane persistence

Switch the control plane fully to local file-backed state and delete the old SQL persistence layer. This keeps runtime storage aligned with the local-first architecture and removes the legacy migration path.

Rhys Sullivan committed 8 months ago

Commit 08365d91 parent 06df963106 files · +2111 −18820Browse files

Unified · Split

106 files
  1. ARCHITECTURE.md +5 −5
  2. PLAN.md +2 −2
  3. README.md +11 −5
  4. apps/executor/src/cli/main.ts +1 −13
  5. apps/executor/src/cli/runtime-paths.ts +0 −18
  6. packages/control-plane/drizzle.config.ts +0 −13
  7. packages/control-plane/drizzle/20260306091536_abnormal_martin_li/migration.sql +0 −158
  8. packages/control-plane/drizzle/20260306091536_abnormal_martin_li/snapshot.json +0 −1937
  9. packages/control-plane/drizzle/20260306094249_large_giant_girl/migration.sql +0 −39
  10. packages/control-plane/drizzle/20260306094249_large_giant_girl/snapshot.json +0 −2425
  11. packages/control-plane/drizzle/20260307090915_blue_bullseye/migration.sql +0 −70
  12. packages/control-plane/drizzle/20260307090915_blue_bullseye/snapshot.json +0 −3122
  13. packages/control-plane/drizzle/20260307134000_openapi_tool_identity/migration.sql +0 −37
  14. packages/control-plane/drizzle/20260307152000_source_document_text/migration.sql +0 −1
  15. packages/control-plane/drizzle/20260308041000_credentials_model/migration.sql +0 −41
  16. packages/control-plane/drizzle/20260308120000_search_and_indexes/migration.sql +0 −16
  17. packages/control-plane/drizzle/20260308140000_secret_materials_name/migration.sql +0 −1
  18. packages/control-plane/drizzle/20260309103000_policy_scope_and_interaction_purpose/migration.sql +0 −17
  19. packages/control-plane/drizzle/20260310120000_source_recipes_and_actor_credentials/migration.sql +0 −491
  20. packages/control-plane/drizzle/20260310183000_source_recipe_schema_bundles/migration.sql +0 −14
  21. packages/control-plane/drizzle/20260310223000_source_adapter_registry_strings/migration.sql +0 −9
  22. packages/control-plane/drizzle/20260310233000_source_adapter_families_and_data_migrations/migration.sql +0 −23
  23. packages/control-plane/drizzle/20260311010000_source_binding_configs/migration.sql +0 −62
  24. packages/control-plane/drizzle/20260311133000_import_auth_slots_and_materialization_hash/migration.sql +0 −82
  25. packages/control-plane/drizzle/20260311170000_thin_source_recipe_operations/migration.sql +0 −73
  26. packages/control-plane/drizzle/20260311223000_drop_tool_artifacts/migration.sql +0 −7
  27. packages/control-plane/drizzle/20260312013000_adapter_keys_and_versioned_binding_configs/migration.sql +0 −55
  28. packages/control-plane/drizzle/20260312113000_auth_artifacts_and_leases/migration.sql +0 −132
  29. packages/control-plane/drizzle/20260312143000_auth_lease_template_placements/migration.sql +0 −40
  30. packages/control-plane/drizzle/20260312233000_execution_steps_and_private_interaction_responses/migration.sql +0 −25
  31. packages/control-plane/src/api/local/http.ts +5 −5
  32. packages/control-plane/src/index.ts +0 −1
  33. packages/control-plane/src/persistence/client.ts +0 −70
  34. packages/control-plane/src/persistence/code-migrations.ts +0 −234
  35. packages/control-plane/src/persistence/index.ts +0 −182
  36. packages/control-plane/src/persistence/persistence-errors.ts +0 −92
  37. packages/control-plane/src/persistence/post-migrations.test.ts +0 −915
  38. packages/control-plane/src/persistence/repos/accounts-repo.ts +0 −71
  39. packages/control-plane/src/persistence/repos/auth-artifacts-repo.ts +0 −226
  40. packages/control-plane/src/persistence/repos/auth-leases-repo.ts +0 −145
  41. packages/control-plane/src/persistence/repos/code-migrations-repo.ts +0 −41
  42. packages/control-plane/src/persistence/repos/execution-interactions-repo.ts +0 −96
  43. packages/control-plane/src/persistence/repos/execution-steps-repo.ts +0 −89
  44. packages/control-plane/src/persistence/repos/executions-repo.ts +0 −75
  45. packages/control-plane/src/persistence/repos/index.ts +0 −16
  46. packages/control-plane/src/persistence/repos/secret-materials-repo.ts +0 −144
  47. packages/control-plane/src/persistence/repos/shared.ts +0 −126
  48. packages/control-plane/src/persistence/repos/source-auth-sessions-repo.ts +0 −163
  49. packages/control-plane/src/persistence/repos/source-oauth-clients-repo.ts +0 −79
  50. packages/control-plane/src/persistence/repos/source-recipe-documents-repo.ts +0 −85
  51. packages/control-plane/src/persistence/repos/source-recipe-operations-repo.ts +0 −86
  52. packages/control-plane/src/persistence/repos/source-recipe-revisions-repo.ts +0 −157
  53. packages/control-plane/src/persistence/repos/source-recipe-schema-bundles-repo.ts +0 −106
  54. packages/control-plane/src/persistence/repos/source-recipes-repo.ts +0 −67
  55. packages/control-plane/src/persistence/repos/sources-repo.ts +0 −154
  56. packages/control-plane/src/persistence/schema.ts +0 −550
  57. packages/control-plane/src/persistence/sql-runtime.ts +0 −206
  58. packages/control-plane/src/runtime/auth-leases.ts +6 −6
  59. packages/control-plane/src/runtime/control-plane-runtime.test.ts +1 −1
  60. packages/control-plane/src/runtime/execution-replay.test.ts +0 −271
  61. packages/control-plane/src/runtime/executor-tools.ts +21 −13
  62. packages/control-plane/src/runtime/index.ts +90 −102
  63. packages/control-plane/src/runtime/live-execution.test.ts +1 −1
  64. packages/control-plane/src/runtime/live-execution.ts +3 −2
  65. packages/control-plane/src/runtime/local-control-plane-store.ts +1054 −0
  66. packages/control-plane/src/runtime/local-operations.ts +16 −15
  67. packages/control-plane/src/runtime/local-storage.ts +154 −0
  68. packages/control-plane/src/runtime/local-tools.ts +1 −1
  69. packages/control-plane/src/runtime/operation-errors.ts +10 −29
  70. packages/control-plane/src/runtime/operations-shared.ts +4 −17
  71. packages/control-plane/src/runtime/policies-operations.ts +35 −11
  72. packages/control-plane/src/runtime/secret-material-providers.ts +16 −14
  73. packages/control-plane/src/runtime/source-adapters/graphql.ts +0 −71
  74. packages/control-plane/src/runtime/source-adapters/mcp.ts +0 −19
  75. packages/control-plane/src/runtime/source-adapters/openapi.ts +0 −70
  76. packages/control-plane/src/runtime/source-adapters/types.ts +2 −11
  77. packages/control-plane/src/runtime/source-auth-material.test.ts +5 −5
  78. packages/control-plane/src/runtime/source-auth-material.ts +2 −2
  79. packages/control-plane/src/runtime/source-auth-service.test.ts +0 −608
  80. packages/control-plane/src/runtime/source-auth-service.ts +27 −25
  81. packages/control-plane/src/runtime/source-definitions.test.ts +11 −11
  82. packages/control-plane/src/runtime/source-inspection.test.ts +0 −344
  83. packages/control-plane/src/runtime/source-materialization.ts +26 −23
  84. packages/control-plane/src/runtime/source-recipe-support.ts +1 −348
  85. packages/control-plane/src/runtime/source-recipes-runtime.test.ts +0 −858
  86. packages/control-plane/src/runtime/source-recipes-runtime.ts +28 −15
  87. packages/control-plane/src/runtime/source-store.test.ts +0 −310
  88. packages/control-plane/src/runtime/source-store.ts +49 −29
  89. packages/control-plane/src/runtime/store.ts +199 −9
  90. packages/control-plane/src/runtime/workspace-execution-environment.test.ts +0 −1986
  91. packages/control-plane/src/runtime/workspace-execution-environment.ts +164 −266
  92. packages/control-plane/src/schema/models/auth-artifact.ts +25 −31
  93. packages/control-plane/src/schema/models/auth-lease.ts +2 −9
  94. packages/control-plane/src/schema/models/auth/account.ts +12 −17
  95. packages/control-plane/src/schema/models/code-migration.ts +5 −8
  96. packages/control-plane/src/schema/models/execution.ts +46 −55
  97. packages/control-plane/src/schema/models/secret-material.ts +3 −9
  98. packages/control-plane/src/schema/models/source-auth-session.ts +5 −9
  99. packages/control-plane/src/schema/models/source-oauth-client.ts +12 −12
  100. packages/control-plane/src/schema/models/source-recipe.ts +34 −45
  101. packages/control-plane/src/schema/models/source.ts +11 −10
  102. packages/control-plane/tsconfig.json +4 −0
  103. packages/kitchen-sink/src/source-runtime.test.ts +2 −2
  104. packages/react/src/index.test.tsx +0 −1
  105. packages/server/src/config.ts +0 −1
  106. packages/server/src/index.ts +0 −4
ARCHITECTURE.md+5−5
@@ -161,19 +161,19 @@ Because of that, these surfaces share: · 160 unmodified lines
161161
162162 ## Persistence model
163163
164−The persistence layer is SQL-backed.
164+The persistence layer is local-file-backed.
165165
166166 Default behavior:
167167
168−- embedded PGlite for local use
168+- workspace config and state are stored in local files
169169
170170 Optional behavior:
171171
172−- Postgres when a Postgres URL is supplied
172+- future cloud backends can plug in behind Effect service boundaries
173173
174−At a high level, the database stores these domains:
174+At a high level, the local control plane stores these domains:
175175
176−- installation identity: local installation, accounts, organizations, memberships, workspaces
176+- installation identity: local installation identity for the workspace
177177 - source state: sources, auth sessions, source credential bindings
178178 - tool model: tool artifacts and related metadata extracted from sources
179179 - secret state: credentials and secret materials
PLAN.md+2−2
@@ -31,7 +31,7 @@ We are on v3 of this codebase which is a completely fresh start, the original co · 30 unmodified lines
3131 Architecture I like:
3232
3333 Database:
34−Postgres SQLIte for local, Postgres SQL for cloud hosted version
34+Local-file-backed control plane for local use, with room for future hosted backends
3535
3636 Server:
3737
@@ -69,7 +69,7 @@ Runtimes: · 31 unmodified lines
6969 Other:
7070
7171 - Avoid hardcoding strings in the core libraries, prefer programming to interfaces, this is because it allows people to build custom adapters without having to modify the core
72−- Aim for composability of adapters. For example, the cloud product may be able to leverage storing secrets both encrypted in postgres and also BYO 1password setup. This does not need to be implemented today but worth noting
72+- Aim for composability of adapters. For example, the cloud product may eventually store secrets in a hosted backend while still supporting BYO 1Password. This does not need to be implemented today but is worth noting.
7373 - This is a fresh start so we can make whatever changes we need.
7474
7575 Rough architecture is: Turborepo monorepo, effect-vitest for testing, bun for package manager / running apps (but we leverage Effect wherever possible rather than bun's apis i.e for the server), Next.js for web app
README.md+11−5
@@ -280,18 +280,16 @@ The server also maintains local PID and log files in its runtime directory. · 279 unmodified lines
280280
281281 ## Persistence and data
282282
283−`executor` persists the local control plane to SQL.
284−
285−By default it uses embedded PGlite for a local install, which keeps the product self-contained. If you provide a Postgres URL, the persistence layer can target real Postgres instead.
283+`executor` persists the local control plane to local files.
286284
287285 Persisted concepts include:
288286
289−- the local installation record
290−- organizations and workspaces provisioned for that install
287+- local installation identity
291288 - connected sources
292289 - indexed tool artifacts and related metadata
293290 - credentials and secret material bindings
294291 - source auth sessions
292+- execution and interaction state
295293 - executions and execution interactions
296294 - policies
297295
@@ -324,6 +322,14 @@ If you are exploring the repo, these are the directories that matter most: · 26 unmodified lines
324322 - `packages/executor-mcp`: MCP bridge for `execute` and `resume`
325323 - `packages/codemode-*`: core tool abstractions plus MCP and OpenAPI adapters
326324
325+## Releasing
326+
327+- `bun run --cwd apps/executor release:publish` is the only supported publish command.
328+- Stable releases use a normal semver like `1.2.3` and publish to npm under `latest`.
329+- Beta releases use a prerelease semver like `1.3.0-beta.1` and publish to npm under `beta`.
330+- Push a matching git tag such as `v1.2.3` or `v1.3.0-beta.1` to trigger `.github/workflows/publish-executor-package.yml`.
331+- To build and pack the publish artifact locally without publishing, run `bun run --cwd apps/executor release:publish:dry-run`.
332+
327333 ## Project status
328334
329335 This repository is explicitly on its third major architecture iteration.
apps/executor/src/cli/main.ts+1−13
@@ -44,7 +44,6 @@ import { · 43 unmodified lines
4444 seedGithubOpenApiSourceInWorkspace,
4545 } from "./dev";
4646 import {
47− resolveRuntimeMigrationsDir,
4847 resolveRuntimeWebAssetsDir,
4948 resolveSelfCommand,
5049 } from "./runtime-paths";
@@ -274,9 +273,7 @@ const buildRunWorkflowText = ( · 223 unmodified lines
274273
275274 const loadRunWorkflowText = (): Effect.Effect<string, Error, never> =>
276275 Effect.acquireUseRelease(
277− createControlPlaneRuntime({
278− localDataDir: DEFAULT_LOCAL_DATA_DIR,
279− }).pipe(Effect.mapError(toError)),
276+ createControlPlaneRuntime({}).pipe(Effect.mapError(toError)),
280277 (runtime) =>
281278 Effect.gen(function* () {
282279 const environment = yield* Effect.gen(function* () {
@@ -414,13 +411,11 @@ const isServerReachable = (baseUrl: string) => · 131 unmodified lines
414411
415412 const getDefaultServerOptions = (port: number = DEFAULT_SERVER_PORT) => {
416413 const assetsDir = resolveRuntimeWebAssetsDir();
417− const migrationsFolder = resolveRuntimeMigrationsDir();
418414
419415 return {
420416 host: DEFAULT_SERVER_HOST,
421417 port,
422418 localDataDir: DEFAULT_LOCAL_DATA_DIR,
423− migrationsFolder: migrationsFolder ?? undefined,
424419 pidFile: DEFAULT_SERVER_PID_FILE,
425420 ui: assetsDir ? { assetsDir } : undefined,
426421 };
@@ -533,7 +528,6 @@ type LocalServerStatus = { · 106 unmodified lines
533528 logFile: string;
534529 localDataDir: string;
535530 webAssetsDir: string | null;
536− migrationsDir: string | null;
537531 installation: {
538532 accountId: string;
539533 workspaceId: string;
@@ -571,7 +565,6 @@ const getServerStatus = (baseUrl: string): Effect.Effect<LocalServerStatus, Erro · 31 unmodified lines
571565 logFile,
572566 localDataDir: DEFAULT_LOCAL_DATA_DIR,
573567 webAssetsDir: resolveRuntimeWebAssetsDir(),
574− migrationsDir: resolveRuntimeMigrationsDir(),
575568 installation,
576569 denoVersion,
577570 } satisfies LocalServerStatus;
@@ -587,7 +580,6 @@ const renderStatus = (status: LocalServerStatus): string => · 9 unmodified lines
587580 `logFile: ${status.logFile}`,
588581 `localDataDir: ${status.localDataDir}`,
589582 `webAssetsDir: ${status.webAssetsDir ?? "missing"}`,
590− `migrationsDir: ${status.migrationsDir ?? "missing"}`,
591583 `workspaceId: ${status.installation?.workspaceId ?? "unavailable"}`,
592584 `denoSandbox: ${renderDenoSandboxDetail(status.denoVersion)}`,
593585 ].join("\n");
@@ -616,10 +608,6 @@ const getDoctorReport = (baseUrl: string) => · 22 unmodified lines
616608 ok: status.webAssetsDir !== null,
617609 detail: status.webAssetsDir ?? "missing bundled web assets",
618610 },
619− migrations: {
620− ok: status.migrationsDir !== null,
621− detail: status.migrationsDir ?? "missing migrations directory",
622− },
623611 installation: {
624612 ok: status.installation !== null,
625613 detail: status.installation
apps/executor/src/cli/runtime-paths.ts+0−18
@@ -3,7 +3,6 @@ import { dirname, extname, resolve } from "node:path"; · 2 unmodified lines
33 import { fileURLToPath } from "node:url";
44
55 import {
6− EXECUTOR_MIGRATIONS_DIR_ENV,
76 EXECUTOR_WEB_ASSETS_DIR_ENV,
87 } from "@executor/server";
98
@@ -93,20 +92,3 @@ export const resolveRuntimeWebAssetsDir = (): string | null => { · 83 unmodified lines
9392
9493 return resolveIfExists(resolve(sourceDir, "../../../web/dist"));
9594 };
96−
97−export const resolveRuntimeMigrationsDir = (): string | null => {
98− const explicit = resolveIfExists(process.env[EXECUTOR_MIGRATIONS_DIR_ENV]);
99− if (explicit) {
100− return explicit;
101− }
102−
103− const resourcesRoot = resolveRuntimeResourcesRoot();
104− const bundled = resourcesRoot
105− ? resolveIfExists(resolve(resourcesRoot, "migrations"))
106− : null;
107− if (bundled) {
108− return bundled;
109− }
110−
111− return resolveIfExists(resolve(sourceDir, "../../../../packages/control-plane/drizzle"));
112−};
packages/control-plane/drizzle.config.ts+0−13
@@ -1,13 +0,0 @@
1−const databaseUrl =
2− process.env.DATABASE_URL ?? "postgres://localhost:5432/executor_v3";
3−
4−export default {
5− dialect: "postgresql",
6− schema: "./src/persistence/schema.ts",
7− out: "./drizzle",
8− dbCredentials: {
9− url: databaseUrl,
10− },
11− strict: true,
12− verbose: true,
13−};
packages/control-plane/drizzle/20260306091536_abnormal_martin_li/migration.sql+0−158
@@ -1,158 +0,0 @@
1−CREATE TABLE "accounts" (
2− "id" text PRIMARY KEY,
3− "provider" text NOT NULL,
4− "subject" text NOT NULL,
5− "email" text,
6− "display_name" text,
7− "created_at" bigint NOT NULL,
8− "updated_at" bigint NOT NULL,
9− CONSTRAINT "accounts_provider_check" CHECK ("provider" in ('local', 'workos', 'service'))
10−);
11−--> statement-breakpoint
12−CREATE TABLE "execution_interactions" (
13− "id" text PRIMARY KEY,
14− "execution_id" text NOT NULL,
15− "status" text NOT NULL,
16− "kind" text NOT NULL,
17− "payload_json" text NOT NULL,
18− "response_json" text,
19− "created_at" bigint NOT NULL,
20− "updated_at" bigint NOT NULL,
21− CONSTRAINT "execution_interactions_status_check" CHECK ("status" in ('pending', 'resolved', 'cancelled'))
22−);
23−--> statement-breakpoint
24−CREATE TABLE "executions" (
25− "id" text PRIMARY KEY,
26− "workspace_id" text NOT NULL,
27− "created_by_account_id" text NOT NULL,
28− "status" text NOT NULL,
29− "code" text NOT NULL,
30− "result_json" text,
31− "error_text" text,
32− "logs_json" text,
33− "started_at" bigint,
34− "completed_at" bigint,
35− "created_at" bigint NOT NULL,
36− "updated_at" bigint NOT NULL,
37− CONSTRAINT "executions_status_check" CHECK ("status" in ('pending', 'running', 'waiting_for_interaction', 'completed', 'failed', 'cancelled'))
38−);
39−--> statement-breakpoint
40−CREATE TABLE "local_installations" (
41− "id" text PRIMARY KEY,
42− "account_id" text NOT NULL,
43− "organization_id" text NOT NULL,
44− "workspace_id" text NOT NULL,
45− "created_at" bigint NOT NULL,
46− "updated_at" bigint NOT NULL
47−);
48−--> statement-breakpoint
49−CREATE TABLE "organization_memberships" (
50− "id" text PRIMARY KEY,
51− "organization_id" text NOT NULL,
52− "account_id" text NOT NULL,
53− "role" text NOT NULL,
54− "status" text NOT NULL,
55− "billable" boolean NOT NULL,
56− "invited_by_account_id" text,
57− "joined_at" bigint,
58− "created_at" bigint NOT NULL,
59− "updated_at" bigint NOT NULL,
60− CONSTRAINT "organization_memberships_role_check" CHECK ("role" in ('viewer', 'editor', 'admin', 'owner')),
61− CONSTRAINT "organization_memberships_status_check" CHECK ("status" in ('invited', 'active', 'suspended', 'removed'))
62−);
63−--> statement-breakpoint
64−CREATE TABLE "organizations" (
65− "id" text PRIMARY KEY,
66− "slug" text NOT NULL,
67− "name" text NOT NULL,
68− "status" text NOT NULL,
69− "created_by_account_id" text,
70− "created_at" bigint NOT NULL,
71− "updated_at" bigint NOT NULL,
72− CONSTRAINT "organizations_status_check" CHECK ("status" in ('active', 'suspended', 'archived'))
73−);
74−--> statement-breakpoint
75−CREATE TABLE "policies" (
76− "id" text PRIMARY KEY,
77− "workspace_id" text NOT NULL,
78− "target_account_id" text,
79− "client_id" text,
80− "resource_type" text NOT NULL,
81− "resource_pattern" text NOT NULL,
82− "match_type" text NOT NULL,
83− "effect" text NOT NULL,
84− "approval_mode" text NOT NULL,
85− "argument_conditions_json" text,
86− "priority" bigint NOT NULL,
87− "enabled" boolean NOT NULL,
88− "created_at" bigint NOT NULL,
89− "updated_at" bigint NOT NULL,
90− CONSTRAINT "policies_resource_type_check" CHECK ("resource_type" in ('all_tools', 'source', 'namespace', 'tool_path')),
91− CONSTRAINT "policies_match_type_check" CHECK ("match_type" in ('glob', 'exact')),
92− CONSTRAINT "policies_effect_check" CHECK ("effect" in ('allow', 'deny')),
93− CONSTRAINT "policies_approval_mode_check" CHECK ("approval_mode" in ('auto', 'required'))
94−);
95−--> statement-breakpoint
96−CREATE TABLE "source_credential_bindings" (
97− "workspace_id" text,
98− "source_id" text,
99− "token_provider_id" text,
100− "token_handle" text,
101− "refresh_token_provider_id" text,
102− "refresh_token_handle" text,
103− "created_at" bigint NOT NULL,
104− "updated_at" bigint NOT NULL,
105− CONSTRAINT "source_credential_bindings_pkey" PRIMARY KEY("workspace_id","source_id")
106−);
107−--> statement-breakpoint
108−CREATE TABLE "sources" (
109− "workspace_id" text,
110− "source_id" text,
111− "name" text NOT NULL,
112− "kind" text NOT NULL,
113− "endpoint" text NOT NULL,
114− "status" text NOT NULL,
115− "enabled" boolean NOT NULL,
116− "namespace" text,
117− "transport" text,
118− "query_params_json" text,
119− "headers_json" text,
120− "spec_url" text,
121− "default_headers_json" text,
122− "auth_kind" text NOT NULL,
123− "auth_header_name" text,
124− "auth_prefix" text,
125− "source_hash" text,
126− "last_error" text,
127− "created_at" bigint NOT NULL,
128− "updated_at" bigint NOT NULL,
129− CONSTRAINT "sources_pkey" PRIMARY KEY("workspace_id","source_id"),
130− CONSTRAINT "sources_kind_check" CHECK ("kind" in ('mcp', 'openapi', 'graphql', 'internal')),
131− CONSTRAINT "sources_status_check" CHECK ("status" in ('draft', 'probing', 'auth_required', 'connected', 'error')),
132− CONSTRAINT "sources_transport_check" CHECK ("transport" is null or "transport" in ('auto', 'streamable-http', 'sse')),
133− CONSTRAINT "sources_auth_kind_check" CHECK ("auth_kind" in ('none', 'bearer', 'oauth2'))
134−);
135−--> statement-breakpoint
136−CREATE TABLE "workspaces" (
137− "id" text PRIMARY KEY,
138− "organization_id" text NOT NULL,
139− "name" text NOT NULL,
140− "created_by_account_id" text,
141− "created_at" bigint NOT NULL,
142− "updated_at" bigint NOT NULL
143−);
144−--> statement-breakpoint
145−CREATE UNIQUE INDEX "accounts_provider_subject_idx" ON "accounts" ("provider","subject");--> statement-breakpoint
146−CREATE INDEX "accounts_updated_idx" ON "accounts" ("updated_at","id");--> statement-breakpoint
147−CREATE INDEX "execution_interactions_execution_idx" ON "execution_interactions" ("execution_id","updated_at","id");--> statement-breakpoint
148−CREATE INDEX "executions_workspace_idx" ON "executions" ("workspace_id","updated_at","id");--> statement-breakpoint
149−CREATE INDEX "organization_memberships_org_idx" ON "organization_memberships" ("organization_id");--> statement-breakpoint
150−CREATE INDEX "organization_memberships_account_idx" ON "organization_memberships" ("account_id");--> statement-breakpoint
151−CREATE UNIQUE INDEX "organization_memberships_org_account_idx" ON "organization_memberships" ("organization_id","account_id");--> statement-breakpoint
152−CREATE UNIQUE INDEX "organizations_slug_idx" ON "organizations" ("slug");--> statement-breakpoint
153−CREATE INDEX "organizations_updated_idx" ON "organizations" ("updated_at","id");--> statement-breakpoint
154−CREATE INDEX "policies_workspace_idx" ON "policies" ("workspace_id","updated_at","id");--> statement-breakpoint
155−CREATE INDEX "source_credential_bindings_workspace_idx" ON "source_credential_bindings" ("workspace_id","updated_at","source_id");--> statement-breakpoint
156−CREATE UNIQUE INDEX "sources_workspace_name_idx" ON "sources" ("workspace_id","name");--> statement-breakpoint
157−CREATE INDEX "workspaces_org_idx" ON "workspaces" ("organization_id");--> statement-breakpoint
158−CREATE UNIQUE INDEX "workspaces_org_name_idx" ON "workspaces" ("organization_id","name");
packages/control-plane/drizzle/20260306091536_abnormal_martin_li/snapshot.json+0−1937
@@ -1,1937 +0,0 @@
1−{
2− "version": "8",
3− "dialect": "postgres",
4− "id": "bac1651d-7a30-4187-a7e2-74bc2b099486",
5− "prevIds": [
6− "00000000-0000-0000-0000-000000000000"
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": "source_credential_bindings",
54− "entityType": "tables",
55− "schema": "public"
56− },
57− {
58− "isRlsEnabled": false,
59− "name": "sources",
60− "entityType": "tables",
61− "schema": "public"
62− },
63− {
64− "isRlsEnabled": false,
65− "name": "workspaces",
66− "entityType": "tables",
67− "schema": "public"
68− },
69− {
70− "type": "text",
71− "typeSchema": null,
72− "notNull": true,
73− "dimensions": 0,
74− "default": null,
75− "generated": null,
76− "identity": null,
77− "name": "id",
78− "entityType": "columns",
79− "schema": "public",
80− "table": "accounts"
81− },
82− {
83− "type": "text",
84− "typeSchema": null,
85− "notNull": true,
86− "dimensions": 0,
87− "default": null,
88− "generated": null,
89− "identity": null,
90− "name": "provider",
91− "entityType": "columns",
92− "schema": "public",
93− "table": "accounts"
94− },
95− {
96− "type": "text",
97− "typeSchema": null,
98− "notNull": true,
99− "dimensions": 0,
100− "default": null,
101− "generated": null,
102− "identity": null,
103− "name": "subject",
104− "entityType": "columns",
105− "schema": "public",
106− "table": "accounts"
107− },
108− {
109− "type": "text",
110− "typeSchema": null,
111− "notNull": false,
112− "dimensions": 0,
113− "default": null,
114− "generated": null,
115− "identity": null,
116− "name": "email",
117− "entityType": "columns",
118− "schema": "public",
119− "table": "accounts"
120− },
121− {
122− "type": "text",
123− "typeSchema": null,
124− "notNull": false,
125− "dimensions": 0,
126− "default": null,
127− "generated": null,
128− "identity": null,
129− "name": "display_name",
130− "entityType": "columns",
131− "schema": "public",
132− "table": "accounts"
133− },
134− {
135− "type": "bigint",
136− "typeSchema": null,
137− "notNull": true,
138− "dimensions": 0,
139− "default": null,
140− "generated": null,
141− "identity": null,
142− "name": "created_at",
143− "entityType": "columns",
144− "schema": "public",
145− "table": "accounts"
146− },
147− {
148− "type": "bigint",
149− "typeSchema": null,
150− "notNull": true,
151− "dimensions": 0,
152− "default": null,
153− "generated": null,
154− "identity": null,
155− "name": "updated_at",
156− "entityType": "columns",
157− "schema": "public",
158− "table": "accounts"
159− },
160− {
161− "type": "text",
162− "typeSchema": null,
163− "notNull": true,
164− "dimensions": 0,
165− "default": null,
166− "generated": null,
167− "identity": null,
168− "name": "id",
169− "entityType": "columns",
170− "schema": "public",
171− "table": "execution_interactions"
172− },
173− {
174− "type": "text",
175− "typeSchema": null,
176− "notNull": true,
177− "dimensions": 0,
178− "default": null,
179− "generated": null,
180− "identity": null,
181− "name": "execution_id",
182− "entityType": "columns",
183− "schema": "public",
184− "table": "execution_interactions"
185− },
186− {
187− "type": "text",
188− "typeSchema": null,
189− "notNull": true,
190− "dimensions": 0,
191− "default": null,
192− "generated": null,
193− "identity": null,
194− "name": "status",
195− "entityType": "columns",
196− "schema": "public",
197− "table": "execution_interactions"
198− },
199− {
200− "type": "text",
201− "typeSchema": null,
202− "notNull": true,
203− "dimensions": 0,
204− "default": null,
205− "generated": null,
206− "identity": null,
207− "name": "kind",
208− "entityType": "columns",
209− "schema": "public",
210− "table": "execution_interactions"
211− },
212− {
213− "type": "text",
214− "typeSchema": null,
215− "notNull": true,
216− "dimensions": 0,
217− "default": null,
218− "generated": null,
219− "identity": null,
220− "name": "payload_json",
221− "entityType": "columns",
222− "schema": "public",
223− "table": "execution_interactions"
224− },
225− {
226− "type": "text",
227− "typeSchema": null,
228− "notNull": false,
229− "dimensions": 0,
230− "default": null,
231− "generated": null,
232− "identity": null,
233− "name": "response_json",
234− "entityType": "columns",
235− "schema": "public",
236− "table": "execution_interactions"
237− },
238− {
239− "type": "bigint",
240− "typeSchema": null,
241− "notNull": true,
242− "dimensions": 0,
243− "default": null,
244− "generated": null,
245− "identity": null,
246− "name": "created_at",
247− "entityType": "columns",
248− "schema": "public",
249− "table": "execution_interactions"
250− },
251− {
252− "type": "bigint",
253− "typeSchema": null,
254− "notNull": true,
255− "dimensions": 0,
256− "default": null,
257− "generated": null,
258− "identity": null,
259− "name": "updated_at",
260− "entityType": "columns",
261− "schema": "public",
262− "table": "execution_interactions"
263− },
264− {
265− "type": "text",
266− "typeSchema": null,
267− "notNull": true,
268− "dimensions": 0,
269− "default": null,
270− "generated": null,
271− "identity": null,
272− "name": "id",
273− "entityType": "columns",
274− "schema": "public",
275− "table": "executions"
276− },
277− {
278− "type": "text",
279− "typeSchema": null,
280− "notNull": true,
281− "dimensions": 0,
282− "default": null,
283− "generated": null,
284− "identity": null,
285− "name": "workspace_id",
286− "entityType": "columns",
287− "schema": "public",
288− "table": "executions"
289− },
290− {
291− "type": "text",
292− "typeSchema": null,
293− "notNull": true,
294− "dimensions": 0,
295− "default": null,
296− "generated": null,
297− "identity": null,
298− "name": "created_by_account_id",
299− "entityType": "columns",
300− "schema": "public",
301− "table": "executions"
302− },
303− {
304− "type": "text",
305− "typeSchema": null,
306− "notNull": true,
307− "dimensions": 0,
308− "default": null,
309− "generated": null,
310− "identity": null,
311− "name": "status",
312− "entityType": "columns",
313− "schema": "public",
314− "table": "executions"
315− },
316− {
317− "type": "text",
318− "typeSchema": null,
319− "notNull": true,
320− "dimensions": 0,
321− "default": null,
322− "generated": null,
323− "identity": null,
324− "name": "code",
325− "entityType": "columns",
326− "schema": "public",
327− "table": "executions"
328− },
329− {
330− "type": "text",
331− "typeSchema": null,
332− "notNull": false,
333− "dimensions": 0,
334− "default": null,
335− "generated": null,
336− "identity": null,
337− "name": "result_json",
338− "entityType": "columns",
339− "schema": "public",
340− "table": "executions"
341− },
342− {
343− "type": "text",
344− "typeSchema": null,
345− "notNull": false,
346− "dimensions": 0,
347− "default": null,
348− "generated": null,
349− "identity": null,
350− "name": "error_text",
351− "entityType": "columns",
352− "schema": "public",
353− "table": "executions"
354− },
355− {
356− "type": "text",
357− "typeSchema": null,
358− "notNull": false,
359− "dimensions": 0,
360− "default": null,
361− "generated": null,
362− "identity": null,
363− "name": "logs_json",
364− "entityType": "columns",
365− "schema": "public",
366− "table": "executions"
367− },
368− {
369− "type": "bigint",
370− "typeSchema": null,
371− "notNull": false,
372− "dimensions": 0,
373− "default": null,
374− "generated": null,
375− "identity": null,
376− "name": "started_at",
377− "entityType": "columns",
378− "schema": "public",
379− "table": "executions"
380− },
381− {
382− "type": "bigint",
383− "typeSchema": null,
384− "notNull": false,
385− "dimensions": 0,
386− "default": null,
387− "generated": null,
388− "identity": null,
389− "name": "completed_at",
390− "entityType": "columns",
391− "schema": "public",
392− "table": "executions"
393− },
394− {
395− "type": "bigint",
396− "typeSchema": null,
397− "notNull": true,
398− "dimensions": 0,
399− "default": null,
400− "generated": null,
401− "identity": null,
402− "name": "created_at",
403− "entityType": "columns",
404− "schema": "public",
405− "table": "executions"
406− },
407− {
408− "type": "bigint",
409− "typeSchema": null,
410− "notNull": true,
411− "dimensions": 0,
412− "default": null,
413− "generated": null,
414− "identity": null,
415− "name": "updated_at",
416− "entityType": "columns",
417− "schema": "public",
418− "table": "executions"
419− },
420− {
421− "type": "text",
422− "typeSchema": null,
423− "notNull": true,
424− "dimensions": 0,
425− "default": null,
426− "generated": null,
427− "identity": null,
428− "name": "id",
429− "entityType": "columns",
430− "schema": "public",
431− "table": "local_installations"
432− },
433− {
434− "type": "text",
435− "typeSchema": null,
436− "notNull": true,
437− "dimensions": 0,
438− "default": null,
439− "generated": null,
440− "identity": null,
441− "name": "account_id",
442− "entityType": "columns",
443− "schema": "public",
444− "table": "local_installations"
445− },
446− {
447− "type": "text",
448− "typeSchema": null,
449− "notNull": true,
450− "dimensions": 0,
451− "default": null,
452− "generated": null,
453− "identity": null,
454− "name": "organization_id",
455− "entityType": "columns",
456− "schema": "public",
457− "table": "local_installations"
458− },
459− {
460− "type": "text",
461− "typeSchema": null,
462− "notNull": true,
463− "dimensions": 0,
464− "default": null,
465− "generated": null,
466− "identity": null,
467− "name": "workspace_id",
468− "entityType": "columns",
469− "schema": "public",
470− "table": "local_installations"
471− },
472− {
473− "type": "bigint",
474− "typeSchema": null,
475− "notNull": true,
476− "dimensions": 0,
477− "default": null,
478− "generated": null,
479− "identity": null,
480− "name": "created_at",
481− "entityType": "columns",
482− "schema": "public",
483− "table": "local_installations"
484− },
485− {
486− "type": "bigint",
487− "typeSchema": null,
488− "notNull": true,
489− "dimensions": 0,
490− "default": null,
491− "generated": null,
492− "identity": null,
493− "name": "updated_at",
494− "entityType": "columns",
495− "schema": "public",
496− "table": "local_installations"
497− },
498− {
499− "type": "text",
500− "typeSchema": null,
501− "notNull": true,
502− "dimensions": 0,
503− "default": null,
504− "generated": null,
505− "identity": null,
506− "name": "id",
507− "entityType": "columns",
508− "schema": "public",
509− "table": "organization_memberships"
510− },
511− {
512− "type": "text",
513− "typeSchema": null,
514− "notNull": true,
515− "dimensions": 0,
516− "default": null,
517− "generated": null,
518− "identity": null,
519− "name": "organization_id",
520− "entityType": "columns",
521− "schema": "public",
522− "table": "organization_memberships"
523− },
524− {
525− "type": "text",
526− "typeSchema": null,
527− "notNull": true,
528− "dimensions": 0,
529− "default": null,
530− "generated": null,
531− "identity": null,
532− "name": "account_id",
533− "entityType": "columns",
534− "schema": "public",
535− "table": "organization_memberships"
536− },
537− {
538− "type": "text",
539− "typeSchema": null,
540− "notNull": true,
541− "dimensions": 0,
542− "default": null,
543− "generated": null,
544− "identity": null,
545− "name": "role",
546− "entityType": "columns",
547− "schema": "public",
548− "table": "organization_memberships"
549− },
550− {
551− "type": "text",
552− "typeSchema": null,
553− "notNull": true,
554− "dimensions": 0,
555− "default": null,
556− "generated": null,
557− "identity": null,
558− "name": "status",
559− "entityType": "columns",
560− "schema": "public",
561− "table": "organization_memberships"
562− },
563− {
564− "type": "boolean",
565− "typeSchema": null,
566− "notNull": true,
567− "dimensions": 0,
568− "default": null,
569− "generated": null,
570− "identity": null,
571− "name": "billable",
572− "entityType": "columns",
573− "schema": "public",
574− "table": "organization_memberships"
575− },
576− {
577− "type": "text",
578− "typeSchema": null,
579− "notNull": false,
580− "dimensions": 0,
581− "default": null,
582− "generated": null,
583− "identity": null,
584− "name": "invited_by_account_id",
585− "entityType": "columns",
586− "schema": "public",
587− "table": "organization_memberships"
588− },
589− {
590− "type": "bigint",
591− "typeSchema": null,
592− "notNull": false,
593− "dimensions": 0,
594− "default": null,
595− "generated": null,
596− "identity": null,
597− "name": "joined_at",
598− "entityType": "columns",
599− "schema": "public",
600− "table": "organization_memberships"
601− },
602− {
603− "type": "bigint",
604− "typeSchema": null,
605− "notNull": true,
606− "dimensions": 0,
607− "default": null,
608− "generated": null,
609− "identity": null,
610− "name": "created_at",
611− "entityType": "columns",
612− "schema": "public",
613− "table": "organization_memberships"
614− },
615− {
616− "type": "bigint",
617− "typeSchema": null,
618− "notNull": true,
619− "dimensions": 0,
620− "default": null,
621− "generated": null,
622− "identity": null,
623− "name": "updated_at",
624− "entityType": "columns",
625− "schema": "public",
626− "table": "organization_memberships"
627− },
628− {
629− "type": "text",
630− "typeSchema": null,
631− "notNull": true,
632− "dimensions": 0,
633− "default": null,
634− "generated": null,
635− "identity": null,
636− "name": "id",
637− "entityType": "columns",
638− "schema": "public",
639− "table": "organizations"
640− },
641− {
642− "type": "text",
643− "typeSchema": null,
644− "notNull": true,
645− "dimensions": 0,
646− "default": null,
647− "generated": null,
648− "identity": null,
649− "name": "slug",
650− "entityType": "columns",
651− "schema": "public",
652− "table": "organizations"
653− },
654− {
655− "type": "text",
656− "typeSchema": null,
657− "notNull": true,
658− "dimensions": 0,
659− "default": null,
660− "generated": null,
661− "identity": null,
662− "name": "name",
663− "entityType": "columns",
664− "schema": "public",
665− "table": "organizations"
666− },
667− {
668− "type": "text",
669− "typeSchema": null,
670− "notNull": true,
671− "dimensions": 0,
672− "default": null,
673− "generated": null,
674− "identity": null,
675− "name": "status",
676− "entityType": "columns",
677− "schema": "public",
678− "table": "organizations"
679− },
680− {
681− "type": "text",
682− "typeSchema": null,
683− "notNull": false,
684− "dimensions": 0,
685− "default": null,
686− "generated": null,
687− "identity": null,
688− "name": "created_by_account_id",
689− "entityType": "columns",
690− "schema": "public",
691− "table": "organizations"
692− },
693− {
694− "type": "bigint",
695− "typeSchema": null,
696− "notNull": true,
697− "dimensions": 0,
698− "default": null,
699− "generated": null,
700− "identity": null,
701− "name": "created_at",
702− "entityType": "columns",
703− "schema": "public",
704− "table": "organizations"
705− },
706− {
707− "type": "bigint",
708− "typeSchema": null,
709− "notNull": true,
710− "dimensions": 0,
711− "default": null,
712− "generated": null,
713− "identity": null,
714− "name": "updated_at",
715− "entityType": "columns",
716− "schema": "public",
717− "table": "organizations"
718− },
719− {
720− "type": "text",
721− "typeSchema": null,
722− "notNull": true,
723− "dimensions": 0,
724− "default": null,
725− "generated": null,
726− "identity": null,
727− "name": "id",
728− "entityType": "columns",
729− "schema": "public",
730− "table": "policies"
731− },
732− {
733− "type": "text",
734− "typeSchema": null,
735− "notNull": true,
736− "dimensions": 0,
737− "default": null,
738− "generated": null,
739− "identity": null,
740− "name": "workspace_id",
741− "entityType": "columns",
742− "schema": "public",
743− "table": "policies"
744− },
745− {
746− "type": "text",
747− "typeSchema": null,
748− "notNull": false,
749− "dimensions": 0,
750− "default": null,
751− "generated": null,
752− "identity": null,
753− "name": "target_account_id",
754− "entityType": "columns",
755− "schema": "public",
756− "table": "policies"
757− },
758− {
759− "type": "text",
760− "typeSchema": null,
761− "notNull": false,
762− "dimensions": 0,
763− "default": null,
764− "generated": null,
765− "identity": null,
766− "name": "client_id",
767− "entityType": "columns",
768− "schema": "public",
769− "table": "policies"
770− },
771− {
772− "type": "text",
773− "typeSchema": null,
774− "notNull": true,
775− "dimensions": 0,
776− "default": null,
777− "generated": null,
778− "identity": null,
779− "name": "resource_type",
780− "entityType": "columns",
781− "schema": "public",
782− "table": "policies"
783− },
784− {
785− "type": "text",
786− "typeSchema": null,
787− "notNull": true,
788− "dimensions": 0,
789− "default": null,
790− "generated": null,
791− "identity": null,
792− "name": "resource_pattern",
793− "entityType": "columns",
794− "schema": "public",
795− "table": "policies"
796− },
797− {
798− "type": "text",
799− "typeSchema": null,
800− "notNull": true,
801− "dimensions": 0,
802− "default": null,
803− "generated": null,
804− "identity": null,
805− "name": "match_type",
806− "entityType": "columns",
807− "schema": "public",
808− "table": "policies"
809− },
810− {
811− "type": "text",
812− "typeSchema": null,
813− "notNull": true,
814− "dimensions": 0,
815− "default": null,
816− "generated": null,
817− "identity": null,
818− "name": "effect",
819− "entityType": "columns",
820− "schema": "public",
821− "table": "policies"
822− },
823− {
824− "type": "text",
825− "typeSchema": null,
826− "notNull": true,
827− "dimensions": 0,
828− "default": null,
829− "generated": null,
830− "identity": null,
831− "name": "approval_mode",
832− "entityType": "columns",
833− "schema": "public",
834− "table": "policies"
835− },
836− {
837− "type": "text",
838− "typeSchema": null,
839− "notNull": false,
840− "dimensions": 0,
841− "default": null,
842− "generated": null,
843− "identity": null,
844− "name": "argument_conditions_json",
845− "entityType": "columns",
846− "schema": "public",
847− "table": "policies"
848− },
849− {
850− "type": "bigint",
851− "typeSchema": null,
852− "notNull": true,
853− "dimensions": 0,
854− "default": null,
855− "generated": null,
856− "identity": null,
857− "name": "priority",
858− "entityType": "columns",
859− "schema": "public",
860− "table": "policies"
861− },
862− {
863− "type": "boolean",
864− "typeSchema": null,
865− "notNull": true,
866− "dimensions": 0,
867− "default": null,
868− "generated": null,
869− "identity": null,
870− "name": "enabled",
871− "entityType": "columns",
872− "schema": "public",
873− "table": "policies"
874− },
875− {
876− "type": "bigint",
877− "typeSchema": null,
878− "notNull": true,
879− "dimensions": 0,
880− "default": null,
881− "generated": null,
882− "identity": null,
883− "name": "created_at",
884− "entityType": "columns",
885− "schema": "public",
886− "table": "policies"
887− },
888− {
889− "type": "bigint",
890− "typeSchema": null,
891− "notNull": true,
892− "dimensions": 0,
893− "default": null,
894− "generated": null,
895− "identity": null,
896− "name": "updated_at",
897− "entityType": "columns",
898− "schema": "public",
899− "table": "policies"
900− },
901− {
902− "type": "text",
903− "typeSchema": null,
904− "notNull": true,
905− "dimensions": 0,
906− "default": null,
907− "generated": null,
908− "identity": null,
909− "name": "workspace_id",
910− "entityType": "columns",
911− "schema": "public",
912− "table": "source_credential_bindings"
913− },
914− {
915− "type": "text",
916− "typeSchema": null,
917− "notNull": true,
918− "dimensions": 0,
919− "default": null,
920− "generated": null,
921− "identity": null,
922− "name": "source_id",
923− "entityType": "columns",
924− "schema": "public",
925− "table": "source_credential_bindings"
926− },
927− {
928− "type": "text",
929− "typeSchema": null,
930− "notNull": false,
931− "dimensions": 0,
932− "default": null,
933− "generated": null,
934− "identity": null,
935− "name": "token_provider_id",
936− "entityType": "columns",
937− "schema": "public",
938− "table": "source_credential_bindings"
939− },
940− {
941− "type": "text",
942− "typeSchema": null,
943− "notNull": false,
944− "dimensions": 0,
945− "default": null,
946− "generated": null,
947− "identity": null,
948− "name": "token_handle",
949− "entityType": "columns",
950− "schema": "public",
951− "table": "source_credential_bindings"
952− },
953− {
954− "type": "text",
955− "typeSchema": null,
956− "notNull": false,
957− "dimensions": 0,
958− "default": null,
959− "generated": null,
960− "identity": null,
961− "name": "refresh_token_provider_id",
962− "entityType": "columns",
963− "schema": "public",
964− "table": "source_credential_bindings"
965− },
966− {
967− "type": "text",
968− "typeSchema": null,
969− "notNull": false,
970− "dimensions": 0,
971− "default": null,
972− "generated": null,
973− "identity": null,
974− "name": "refresh_token_handle",
975− "entityType": "columns",
976− "schema": "public",
977− "table": "source_credential_bindings"
978− },
979− {
980− "type": "bigint",
981− "typeSchema": null,
982− "notNull": true,
983− "dimensions": 0,
984− "default": null,
985− "generated": null,
986− "identity": null,
987− "name": "created_at",
988− "entityType": "columns",
989− "schema": "public",
990− "table": "source_credential_bindings"
991− },
992− {
993− "type": "bigint",
994− "typeSchema": null,
995− "notNull": true,
996− "dimensions": 0,
997− "default": null,
998− "generated": null,
999− "identity": null,
1000− "name": "updated_at",
1001− "entityType": "columns",
1002− "schema": "public",
1003− "table": "source_credential_bindings"
1004− },
1005− {
1006− "type": "text",
1007− "typeSchema": null,
1008− "notNull": true,
1009− "dimensions": 0,
1010− "default": null,
1011− "generated": null,
1012− "identity": null,
1013− "name": "workspace_id",
1014− "entityType": "columns",
1015− "schema": "public",
1016− "table": "sources"
1017− },
1018− {
1019− "type": "text",
1020− "typeSchema": null,
1021− "notNull": true,
1022− "dimensions": 0,
1023− "default": null,
1024− "generated": null,
1025− "identity": null,
1026− "name": "source_id",
1027− "entityType": "columns",
1028− "schema": "public",
1029− "table": "sources"
1030− },
1031− {
1032− "type": "text",
1033− "typeSchema": null,
1034− "notNull": true,
1035− "dimensions": 0,
1036− "default": null,
1037− "generated": null,
1038− "identity": null,
1039− "name": "name",
1040− "entityType": "columns",
1041− "schema": "public",
1042− "table": "sources"
1043− },
1044− {
1045− "type": "text",
1046− "typeSchema": null,
1047− "notNull": true,
1048− "dimensions": 0,
1049− "default": null,
1050− "generated": null,
1051− "identity": null,
1052− "name": "kind",
1053− "entityType": "columns",
1054− "schema": "public",
1055− "table": "sources"
1056− },
1057− {
1058− "type": "text",
1059− "typeSchema": null,
1060− "notNull": true,
1061− "dimensions": 0,
1062− "default": null,
1063− "generated": null,
1064− "identity": null,
1065− "name": "endpoint",
1066− "entityType": "columns",
1067− "schema": "public",
1068− "table": "sources"
1069− },
1070− {
1071− "type": "text",
1072− "typeSchema": null,
1073− "notNull": true,
1074− "dimensions": 0,
1075− "default": null,
1076− "generated": null,
1077− "identity": null,
1078− "name": "status",
1079− "entityType": "columns",
1080− "schema": "public",
1081− "table": "sources"
1082− },
1083− {
1084− "type": "boolean",
1085− "typeSchema": null,
1086− "notNull": true,
1087− "dimensions": 0,
1088− "default": null,
1089− "generated": null,
1090− "identity": null,
1091− "name": "enabled",
1092− "entityType": "columns",
1093− "schema": "public",
1094− "table": "sources"
1095− },
1096− {
1097− "type": "text",
1098− "typeSchema": null,
1099− "notNull": false,
1100− "dimensions": 0,
1101− "default": null,
1102− "generated": null,
1103− "identity": null,
1104− "name": "namespace",
1105− "entityType": "columns",
1106− "schema": "public",
1107− "table": "sources"
1108− },
1109− {
1110− "type": "text",
1111− "typeSchema": null,
1112− "notNull": false,
1113− "dimensions": 0,
1114− "default": null,
1115− "generated": null,
1116− "identity": null,
1117− "name": "transport",
1118− "entityType": "columns",
1119− "schema": "public",
1120− "table": "sources"
1121− },
1122− {
1123− "type": "text",
1124− "typeSchema": null,
1125− "notNull": false,
1126− "dimensions": 0,
1127− "default": null,
1128− "generated": null,
1129− "identity": null,
1130− "name": "query_params_json",
1131− "entityType": "columns",
1132− "schema": "public",
1133− "table": "sources"
1134− },
1135− {
1136− "type": "text",
1137− "typeSchema": null,
1138− "notNull": false,
1139− "dimensions": 0,
1140− "default": null,
1141− "generated": null,
1142− "identity": null,
1143− "name": "headers_json",
1144− "entityType": "columns",
1145− "schema": "public",
1146− "table": "sources"
1147− },
1148− {
1149− "type": "text",
1150− "typeSchema": null,
1151− "notNull": false,
1152− "dimensions": 0,
1153− "default": null,
1154− "generated": null,
1155− "identity": null,
1156− "name": "spec_url",
1157− "entityType": "columns",
1158− "schema": "public",
1159− "table": "sources"
1160− },
1161− {
1162− "type": "text",
1163− "typeSchema": null,
1164− "notNull": false,
1165− "dimensions": 0,
1166− "default": null,
1167− "generated": null,
1168− "identity": null,
1169− "name": "default_headers_json",
1170− "entityType": "columns",
1171− "schema": "public",
1172− "table": "sources"
1173− },
1174− {
1175− "type": "text",
1176− "typeSchema": null,
1177− "notNull": true,
1178− "dimensions": 0,
1179− "default": null,
1180− "generated": null,
1181− "identity": null,
1182− "name": "auth_kind",
1183− "entityType": "columns",
1184− "schema": "public",
1185− "table": "sources"
1186− },
1187− {
1188− "type": "text",
1189− "typeSchema": null,
1190− "notNull": false,
1191− "dimensions": 0,
1192− "default": null,
1193− "generated": null,
1194− "identity": null,
1195− "name": "auth_header_name",
1196− "entityType": "columns",
1197− "schema": "public",
1198− "table": "sources"
1199− },
1200− {
1201− "type": "text",
1202− "typeSchema": null,
1203− "notNull": false,
1204− "dimensions": 0,
1205− "default": null,
1206− "generated": null,
1207− "identity": null,
1208− "name": "auth_prefix",
1209− "entityType": "columns",
1210− "schema": "public",
1211− "table": "sources"
1212− },
1213− {
1214− "type": "text",
1215− "typeSchema": null,
1216− "notNull": false,
1217− "dimensions": 0,
1218− "default": null,
1219− "generated": null,
1220− "identity": null,
1221− "name": "source_hash",
1222− "entityType": "columns",
1223− "schema": "public",
1224− "table": "sources"
1225− },
1226− {
1227− "type": "text",
1228− "typeSchema": null,
1229− "notNull": false,
1230− "dimensions": 0,
1231− "default": null,
1232− "generated": null,
1233− "identity": null,
1234− "name": "last_error",
1235− "entityType": "columns",
1236− "schema": "public",
1237− "table": "sources"
1238− },
1239− {
1240− "type": "bigint",
1241− "typeSchema": null,
1242− "notNull": true,
1243− "dimensions": 0,
1244− "default": null,
1245− "generated": null,
1246− "identity": null,
1247− "name": "created_at",
1248− "entityType": "columns",
1249− "schema": "public",
1250− "table": "sources"
1251− },
1252− {
1253− "type": "bigint",
1254− "typeSchema": null,
1255− "notNull": true,
1256− "dimensions": 0,
1257− "default": null,
1258− "generated": null,
1259− "identity": null,
1260− "name": "updated_at",
1261− "entityType": "columns",
1262− "schema": "public",
1263− "table": "sources"
1264− },
1265− {
1266− "type": "text",
1267− "typeSchema": null,
1268− "notNull": true,
1269− "dimensions": 0,
1270− "default": null,
1271− "generated": null,
1272− "identity": null,
1273− "name": "id",
1274− "entityType": "columns",
1275− "schema": "public",
1276− "table": "workspaces"
1277− },
1278− {
1279− "type": "text",
1280− "typeSchema": null,
1281− "notNull": true,
1282− "dimensions": 0,
1283− "default": null,
1284− "generated": null,
1285− "identity": null,
1286− "name": "organization_id",
1287− "entityType": "columns",
1288− "schema": "public",
1289− "table": "workspaces"
1290− },
1291− {
1292− "type": "text",
1293− "typeSchema": null,
1294− "notNull": true,
1295− "dimensions": 0,
1296− "default": null,
1297− "generated": null,
1298− "identity": null,
1299− "name": "name",
1300− "entityType": "columns",
1301− "schema": "public",
1302− "table": "workspaces"
1303− },
1304− {
1305− "type": "text",
1306− "typeSchema": null,
1307− "notNull": false,
1308− "dimensions": 0,
1309− "default": null,
1310− "generated": null,
1311− "identity": null,
1312− "name": "created_by_account_id",
1313− "entityType": "columns",
1314− "schema": "public",
1315− "table": "workspaces"
1316− },
1317− {
1318− "type": "bigint",
1319− "typeSchema": null,
1320− "notNull": true,
1321− "dimensions": 0,
1322− "default": null,
1323− "generated": null,
1324− "identity": null,
1325− "name": "created_at",
1326− "entityType": "columns",
1327− "schema": "public",
1328− "table": "workspaces"
1329− },
1330− {
1331− "type": "bigint",
1332− "typeSchema": null,
1333− "notNull": true,
1334− "dimensions": 0,
1335− "default": null,
1336− "generated": null,
1337− "identity": null,
1338− "name": "updated_at",
1339− "entityType": "columns",
1340− "schema": "public",
1341− "table": "workspaces"
1342− },
1343− {
1344− "nameExplicit": true,
1345− "columns": [
1346− {
1347− "value": "provider",
1348− "isExpression": false,
1349− "asc": true,
1350− "nullsFirst": false,
1351− "opclass": null
1352− },
1353− {
1354− "value": "subject",
1355− "isExpression": false,
1356− "asc": true,
1357− "nullsFirst": false,
1358− "opclass": null
1359− }
1360− ],
1361− "isUnique": true,
1362− "where": null,
1363− "with": "",
1364− "method": "btree",
1365− "concurrently": false,
1366− "name": "accounts_provider_subject_idx",
1367− "entityType": "indexes",
1368− "schema": "public",
1369− "table": "accounts"
1370− },
1371− {
1372− "nameExplicit": true,
1373− "columns": [
1374− {
1375− "value": "updated_at",
1376− "isExpression": false,
1377− "asc": true,
1378− "nullsFirst": false,
1379− "opclass": null
1380− },
1381− {
1382− "value": "id",
1383− "isExpression": false,
1384− "asc": true,
1385− "nullsFirst": false,
1386− "opclass": null
1387− }
1388− ],
1389− "isUnique": false,
1390− "where": null,
1391− "with": "",
1392− "method": "btree",
1393− "concurrently": false,
1394− "name": "accounts_updated_idx",
1395− "entityType": "indexes",
1396− "schema": "public",
1397− "table": "accounts"
1398− },
1399− {
1400− "nameExplicit": true,
1401− "columns": [
1402− {
1403− "value": "execution_id",
1404− "isExpression": false,
1405− "asc": true,
1406− "nullsFirst": false,
1407− "opclass": null
1408− },
1409− {
1410− "value": "updated_at",
1411− "isExpression": false,
1412− "asc": true,
1413− "nullsFirst": false,
1414− "opclass": null
1415− },
1416− {
1417− "value": "id",
1418− "isExpression": false,
1419− "asc": true,
1420− "nullsFirst": false,
1421− "opclass": null
1422− }
1423− ],
1424− "isUnique": false,
1425− "where": null,
1426− "with": "",
1427− "method": "btree",
1428− "concurrently": false,
1429− "name": "execution_interactions_execution_idx",
1430− "entityType": "indexes",
1431− "schema": "public",
1432− "table": "execution_interactions"
1433− },
1434− {
1435− "nameExplicit": true,
1436− "columns": [
1437− {
1438− "value": "workspace_id",
1439− "isExpression": false,
1440− "asc": true,
1441− "nullsFirst": false,
1442− "opclass": null
1443− },
1444− {
1445− "value": "updated_at",
1446− "isExpression": false,
1447− "asc": true,
1448− "nullsFirst": false,
1449− "opclass": null
1450− },
1451− {
1452− "value": "id",
1453− "isExpression": false,
1454− "asc": true,
1455− "nullsFirst": false,
1456− "opclass": null
1457− }
1458− ],
1459− "isUnique": false,
1460− "where": null,
1461− "with": "",
1462− "method": "btree",
1463− "concurrently": false,
1464− "name": "executions_workspace_idx",
1465− "entityType": "indexes",
1466− "schema": "public",
1467− "table": "executions"
1468− },
1469− {
1470− "nameExplicit": true,
1471− "columns": [
1472− {
1473− "value": "organization_id",
1474− "isExpression": false,
1475− "asc": true,
1476− "nullsFirst": false,
1477− "opclass": null
1478− }
1479− ],
1480− "isUnique": false,
1481− "where": null,
1482− "with": "",
1483− "method": "btree",
1484− "concurrently": false,
1485− "name": "organization_memberships_org_idx",
1486− "entityType": "indexes",
1487− "schema": "public",
1488− "table": "organization_memberships"
1489− },
1490− {
1491− "nameExplicit": true,
1492− "columns": [
1493− {
1494− "value": "account_id",
1495− "isExpression": false,
1496− "asc": true,
1497− "nullsFirst": false,
1498− "opclass": null
1499− }
1500− ],
1501− "isUnique": false,
1502− "where": null,
1503− "with": "",
1504− "method": "btree",
1505− "concurrently": false,
1506− "name": "organization_memberships_account_idx",
1507− "entityType": "indexes",
1508− "schema": "public",
1509− "table": "organization_memberships"
1510− },
1511− {
1512− "nameExplicit": true,
1513− "columns": [
1514− {
1515− "value": "organization_id",
1516− "isExpression": false,
1517− "asc": true,
1518− "nullsFirst": false,
1519− "opclass": null
1520− },
1521− {
1522− "value": "account_id",
1523− "isExpression": false,
1524− "asc": true,
1525− "nullsFirst": false,
1526− "opclass": null
1527− }
1528− ],
1529− "isUnique": true,
1530− "where": null,
1531− "with": "",
1532− "method": "btree",
1533− "concurrently": false,
1534− "name": "organization_memberships_org_account_idx",
1535− "entityType": "indexes",
1536− "schema": "public",
1537− "table": "organization_memberships"
1538− },
1539− {
1540− "nameExplicit": true,
1541− "columns": [
1542− {
1543− "value": "slug",
1544− "isExpression": false,
1545− "asc": true,
1546− "nullsFirst": false,
1547− "opclass": null
1548− }
1549− ],
1550− "isUnique": true,
1551− "where": null,
1552− "with": "",
1553− "method": "btree",
1554− "concurrently": false,
1555− "name": "organizations_slug_idx",
1556− "entityType": "indexes",
1557− "schema": "public",
1558− "table": "organizations"
1559− },
1560− {
1561− "nameExplicit": true,
1562− "columns": [
1563− {
1564− "value": "updated_at",
1565− "isExpression": false,
1566− "asc": true,
1567− "nullsFirst": false,
1568− "opclass": null
1569− },
1570− {
1571− "value": "id",
1572− "isExpression": false,
1573− "asc": true,
1574− "nullsFirst": false,
1575− "opclass": null
1576− }
1577− ],
1578− "isUnique": false,
1579− "where": null,
1580− "with": "",
1581− "method": "btree",
1582− "concurrently": false,
1583− "name": "organizations_updated_idx",
1584− "entityType": "indexes",
1585− "schema": "public",
1586− "table": "organizations"
1587− },
1588− {
1589− "nameExplicit": true,
1590− "columns": [
1591− {
1592− "value": "workspace_id",
1593− "isExpression": false,
1594− "asc": true,
1595− "nullsFirst": false,
1596− "opclass": null
1597− },
1598− {
1599− "value": "updated_at",
1600− "isExpression": false,
1601− "asc": true,
1602− "nullsFirst": false,
1603− "opclass": null
1604− },
1605− {
1606− "value": "id",
1607− "isExpression": false,
1608− "asc": true,
1609− "nullsFirst": false,
1610− "opclass": null
1611− }
1612− ],
1613− "isUnique": false,
1614− "where": null,
1615− "with": "",
1616− "method": "btree",
1617− "concurrently": false,
1618− "name": "policies_workspace_idx",
1619− "entityType": "indexes",
1620− "schema": "public",
1621− "table": "policies"
1622− },
1623− {
1624− "nameExplicit": true,
1625− "columns": [
1626− {
1627− "value": "workspace_id",
1628− "isExpression": false,
1629− "asc": true,
1630− "nullsFirst": false,
1631− "opclass": null
1632− },
1633− {
1634− "value": "updated_at",
1635− "isExpression": false,
1636− "asc": true,
1637− "nullsFirst": false,
1638− "opclass": null
1639− },
1640− {
1641− "value": "source_id",
1642− "isExpression": false,
1643− "asc": true,
1644− "nullsFirst": false,
1645− "opclass": null
1646− }
1647− ],
1648− "isUnique": false,
1649− "where": null,
1650− "with": "",
1651− "method": "btree",
1652− "concurrently": false,
1653− "name": "source_credential_bindings_workspace_idx",
1654− "entityType": "indexes",
1655− "schema": "public",
1656− "table": "source_credential_bindings"
1657− },
1658− {
1659− "nameExplicit": true,
1660− "columns": [
1661− {
1662− "value": "workspace_id",
1663− "isExpression": false,
1664− "asc": true,
1665− "nullsFirst": false,
1666− "opclass": null
1667− },
1668− {
1669− "value": "name",
1670− "isExpression": false,
1671− "asc": true,
1672− "nullsFirst": false,
1673− "opclass": null
1674− }
1675− ],
1676− "isUnique": true,
1677− "where": null,
1678− "with": "",
1679− "method": "btree",
1680− "concurrently": false,
1681− "name": "sources_workspace_name_idx",
1682− "entityType": "indexes",
1683− "schema": "public",
1684− "table": "sources"
1685− },
1686− {
1687− "nameExplicit": true,
1688− "columns": [
1689− {
1690− "value": "organization_id",
1691− "isExpression": false,
1692− "asc": true,
1693− "nullsFirst": false,
1694− "opclass": null
1695− }
1696− ],
1697− "isUnique": false,
1698− "where": null,
1699− "with": "",
1700− "method": "btree",
1701− "concurrently": false,
1702− "name": "workspaces_org_idx",
1703− "entityType": "indexes",
1704− "schema": "public",
1705− "table": "workspaces"
1706− },
1707− {
1708− "nameExplicit": true,
1709− "columns": [
1710− {
1711− "value": "organization_id",
1712− "isExpression": false,
1713− "asc": true,
1714− "nullsFirst": false,
1715− "opclass": null
1716− },
1717− {
1718− "value": "name",
1719− "isExpression": false,
1720− "asc": true,
1721− "nullsFirst": false,
1722− "opclass": null
1723− }
1724− ],
1725− "isUnique": true,
1726− "where": null,
1727− "with": "",
1728− "method": "btree",
1729− "concurrently": false,
1730− "name": "workspaces_org_name_idx",
1731− "entityType": "indexes",
1732− "schema": "public",
1733− "table": "workspaces"
1734− },
1735− {
1736− "columns": [
1737− "workspace_id",
1738− "source_id"
1739− ],
1740− "nameExplicit": false,
1741− "name": "source_credential_bindings_pkey",
1742− "entityType": "pks",
1743− "schema": "public",
1744− "table": "source_credential_bindings"
1745− },
1746− {
1747− "columns": [
1748− "workspace_id",
1749− "source_id"
1750− ],
1751− "nameExplicit": false,
1752− "name": "sources_pkey",
1753− "entityType": "pks",
1754− "schema": "public",
1755− "table": "sources"
1756− },
1757− {
1758− "columns": [
1759− "id"
1760− ],
1761− "nameExplicit": false,
1762− "name": "accounts_pkey",
1763− "schema": "public",
1764− "table": "accounts",
1765− "entityType": "pks"
1766− },
1767− {
1768− "columns": [
1769− "id"
1770− ],
1771− "nameExplicit": false,
1772− "name": "execution_interactions_pkey",
1773− "schema": "public",
1774− "table": "execution_interactions",
1775− "entityType": "pks"
1776− },
1777− {
1778− "columns": [
1779− "id"
1780− ],
1781− "nameExplicit": false,
1782− "name": "executions_pkey",
1783− "schema": "public",
1784− "table": "executions",
1785− "entityType": "pks"
1786− },
1787− {
1788− "columns": [
1789− "id"
1790− ],
1791− "nameExplicit": false,
1792− "name": "local_installations_pkey",
1793− "schema": "public",
1794− "table": "local_installations",
1795− "entityType": "pks"
1796− },
1797− {
1798− "columns": [
1799− "id"
1800− ],
1801− "nameExplicit": false,
1802− "name": "organization_memberships_pkey",
1803− "schema": "public",
1804− "table": "organization_memberships",
1805− "entityType": "pks"
1806− },
1807− {
1808− "columns": [
1809− "id"
1810− ],
1811− "nameExplicit": false,
1812− "name": "organizations_pkey",
1813− "schema": "public",
1814− "table": "organizations",
1815− "entityType": "pks"
1816− },
1817− {
1818− "columns": [
1819− "id"
1820− ],
1821− "nameExplicit": false,
1822− "name": "policies_pkey",
1823− "schema": "public",
1824− "table": "policies",
1825− "entityType": "pks"
1826− },
1827− {
1828− "columns": [
1829− "id"
1830− ],
1831− "nameExplicit": false,
1832− "name": "workspaces_pkey",
1833− "schema": "public",
1834− "table": "workspaces",
1835− "entityType": "pks"
1836− },
1837− {
1838− "value": "\"provider\" in ('local', 'workos', 'service')",
1839− "name": "accounts_provider_check",
1840− "entityType": "checks",
1841− "schema": "public",
1842− "table": "accounts"
1843− },
1844− {
1845− "value": "\"status\" in ('pending', 'resolved', 'cancelled')",
1846− "name": "execution_interactions_status_check",
1847− "entityType": "checks",
1848− "schema": "public",
1849− "table": "execution_interactions"
1850− },
1851− {
1852− "value": "\"status\" in ('pending', 'running', 'waiting_for_interaction', 'completed', 'failed', 'cancelled')",
1853− "name": "executions_status_check",
1854− "entityType": "checks",
1855− "schema": "public",
1856− "table": "executions"
1857− },
1858− {
1859− "value": "\"role\" in ('viewer', 'editor', 'admin', 'owner')",
1860− "name": "organization_memberships_role_check",
1861− "entityType": "checks",
1862− "schema": "public",
1863− "table": "organization_memberships"
1864− },
1865− {
1866− "value": "\"status\" in ('invited', 'active', 'suspended', 'removed')",
1867− "name": "organization_memberships_status_check",
1868− "entityType": "checks",
1869− "schema": "public",
1870− "table": "organization_memberships"
1871− },
1872− {
1873− "value": "\"status\" in ('active', 'suspended', 'archived')",
1874− "name": "organizations_status_check",
1875− "entityType": "checks",
1876− "schema": "public",
1877− "table": "organizations"
1878− },
1879− {
1880− "value": "\"resource_type\" in ('all_tools', 'source', 'namespace', 'tool_path')",
1881− "name": "policies_resource_type_check",
1882− "entityType": "checks",
1883− "schema": "public",
1884− "table": "policies"
1885− },
1886− {
1887− "value": "\"match_type\" in ('glob', 'exact')",
1888− "name": "policies_match_type_check",
1889− "entityType": "checks",
1890− "schema": "public",
1891− "table": "policies"
1892− },
1893− {
1894− "value": "\"effect\" in ('allow', 'deny')",
1895− "name": "policies_effect_check",
1896− "entityType": "checks",
1897− "schema": "public",
1898− "table": "policies"
1899− },
1900− {
1901− "value": "\"approval_mode\" in ('auto', 'required')",
1902− "name": "policies_approval_mode_check",
1903− "entityType": "checks",
1904− "schema": "public",
1905− "table": "policies"
1906− },
1907− {
1908− "value": "\"kind\" in ('mcp', 'openapi', 'graphql', 'internal')",
1909− "name": "sources_kind_check",
1910− "entityType": "checks",
1911− "schema": "public",
1912− "table": "sources"
1913− },
1914− {
1915− "value": "\"status\" in ('draft', 'probing', 'auth_required', 'connected', 'error')",
1916− "name": "sources_status_check",
1917− "entityType": "checks",
1918− "schema": "public",
1919− "table": "sources"
1920− },
1921− {
1922− "value": "\"transport\" is null or \"transport\" in ('auto', 'streamable-http', 'sse')",
1923− "name": "sources_transport_check",
1924− "entityType": "checks",
1925− "schema": "public",
1926− "table": "sources"
1927− },
1928− {
1929− "value": "\"auth_kind\" in ('none', 'bearer', 'oauth2')",
1930− "name": "sources_auth_kind_check",
1931− "entityType": "checks",
1932− "schema": "public",
1933− "table": "sources"
1934− }
1935− ],
1936− "renames": []
1937−}
packages/control-plane/drizzle/20260306094249_large_giant_girl/migration.sql+0−39
@@ -1,39 +0,0 @@
1−CREATE 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
10−CREATE 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
37−CREATE INDEX "secret_materials_updated_idx" ON "secret_materials" ("updated_at","id");--> statement-breakpoint
38−CREATE INDEX "source_auth_sessions_workspace_idx" ON "source_auth_sessions" ("workspace_id","updated_at","id");--> statement-breakpoint
39−CREATE UNIQUE INDEX "source_auth_sessions_state_idx" ON "source_auth_sessions" ("state");
packages/control-plane/drizzle/20260306094249_large_giant_girl/snapshot.json+0−2425
@@ -1,2425 +0,0 @@
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/drizzle/20260307090915_blue_bullseye/migration.sql+0−70
@@ -1,70 +0,0 @@
1−CREATE TABLE "tool_artifact_parameters" (
2− "workspace_id" text,
3− "path" text,
4− "position" bigint,
5− "name" text NOT NULL,
6− "location" text NOT NULL,
7− "required" boolean NOT NULL,
8− CONSTRAINT "tool_artifact_parameters_pkey" PRIMARY KEY("workspace_id","path","position"),
9− CONSTRAINT "tool_artifact_parameters_location_check" CHECK ("location" in ('path', 'query', 'header', 'cookie'))
10−);
11−--> statement-breakpoint
12−CREATE TABLE "tool_artifact_ref_hint_keys" (
13− "workspace_id" text,
14− "path" text,
15− "position" bigint,
16− "ref_hint_key" text NOT NULL,
17− CONSTRAINT "tool_artifact_ref_hint_keys_pkey" PRIMARY KEY("workspace_id","path","position")
18−);
19−--> statement-breakpoint
20−CREATE TABLE "tool_artifact_request_body_content_types" (
21− "workspace_id" text,
22− "path" text,
23− "position" bigint,
24− "content_type" text NOT NULL,
25− CONSTRAINT "tool_artifact_request_body_content_types_pkey" PRIMARY KEY("workspace_id","path","position")
26−);
27−--> statement-breakpoint
28−CREATE TABLE "tool_artifacts" (
29− "workspace_id" text,
30− "path" text,
31− "tool_id" text NOT NULL,
32− "source_id" text NOT NULL,
33− "title" text,
34− "description" text,
35− "search_namespace" text NOT NULL,
36− "search_text" text NOT NULL,
37− "input_schema_json" text,
38− "output_schema_json" text,
39− "provider_kind" text NOT NULL,
40− "mcp_tool_name" text,
41− "openapi_method" text,
42− "openapi_path_template" text,
43− "openapi_operation_hash" text,
44− "openapi_request_body_required" boolean,
45− "created_at" bigint NOT NULL,
46− "updated_at" bigint NOT NULL,
47− CONSTRAINT "tool_artifacts_pkey" PRIMARY KEY("workspace_id","path"),
48− CONSTRAINT "tool_artifacts_provider_kind_check" CHECK ("provider_kind" in ('mcp', 'openapi')),
49− CONSTRAINT "tool_artifacts_mcp_shape_check" CHECK ("provider_kind" <> 'mcp'
50− or (
51− "mcp_tool_name" is not null
52− and "openapi_method" is null
53− and "openapi_path_template" is null
54− and "openapi_operation_hash" is null
55− and "openapi_request_body_required" is null
56− )),
57− CONSTRAINT "tool_artifacts_openapi_shape_check" CHECK ("provider_kind" <> 'openapi'
58− or (
59− "mcp_tool_name" is null
60− and "openapi_method" in ('get', 'put', 'post', 'delete', 'patch', 'head', 'options', 'trace')
61− and "openapi_path_template" is not null
62− and "openapi_operation_hash" is not null
63− ))
64−);
65−--> statement-breakpoint
66−CREATE INDEX "tool_artifact_parameters_lookup_idx" ON "tool_artifact_parameters" ("workspace_id","path","position");--> statement-breakpoint
67−CREATE INDEX "tool_artifact_ref_hint_keys_lookup_idx" ON "tool_artifact_ref_hint_keys" ("workspace_id","path","position");--> statement-breakpoint
68−CREATE INDEX "tool_artifact_request_body_content_types_lookup_idx" ON "tool_artifact_request_body_content_types" ("workspace_id","path","position");--> statement-breakpoint
69−CREATE INDEX "tool_artifacts_workspace_source_idx" ON "tool_artifacts" ("workspace_id","source_id","updated_at","path");--> statement-breakpoint
70−CREATE INDEX "tool_artifacts_workspace_namespace_idx" ON "tool_artifacts" ("workspace_id","search_namespace","path");
packages/control-plane/drizzle/20260307090915_blue_bullseye/snapshot.json+0−3122
@@ -1,3122 +0,0 @@
1−{
2− "version": "8",
3− "dialect": "postgres",
4− "id": "3e9aa0ec-194c-4364-9e57-4539db8fb2ad",
5− "prevIds": [
6− "1141497b-c500-44ff-87db-6a440062becb"
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": "tool_artifact_parameters",
78− "entityType": "tables",
79− "schema": "public"
80− },
81− {
82− "isRlsEnabled": false,
83− "name": "tool_artifact_ref_hint_keys",
84− "entityType": "tables",
85− "schema": "public"
86− },
87− {
88− "isRlsEnabled": false,
89− "name": "tool_artifact_request_body_content_types",
90− "entityType": "tables",
91− "schema": "public"
92− },
93− {
94− "isRlsEnabled": false,
95− "name": "tool_artifacts",
96− "entityType": "tables",
97− "schema": "public"
98− },
99− {
100− "isRlsEnabled": false,
101− "name": "workspaces",
102− "entityType": "tables",
103− "schema": "public"
104− },
105− {
106− "type": "text",
107− "typeSchema": null,
108− "notNull": true,
109− "dimensions": 0,
110− "default": null,
111− "generated": null,
112− "identity": null,
113− "name": "id",
114− "entityType": "columns",
115− "schema": "public",
116− "table": "accounts"
117− },
118− {
119− "type": "text",
120− "typeSchema": null,
121− "notNull": true,
122− "dimensions": 0,
123− "default": null,
124− "generated": null,
125− "identity": null,
126− "name": "provider",
127− "entityType": "columns",
128− "schema": "public",
129− "table": "accounts"
130− },
131− {
132− "type": "text",
133− "typeSchema": null,
134− "notNull": true,
135− "dimensions": 0,
136− "default": null,
137− "generated": null,
138− "identity": null,
139− "name": "subject",
140− "entityType": "columns",
141− "schema": "public",
142− "table": "accounts"
143− },
144− {
145− "type": "text",
146− "typeSchema": null,
147− "notNull": false,
148− "dimensions": 0,
149− "default": null,
150− "generated": null,
151− "identity": null,
152− "name": "email",
153− "entityType": "columns",
154− "schema": "public",
155− "table": "accounts"
156− },
157− {
158− "type": "text",
159− "typeSchema": null,
160− "notNull": false,
161− "dimensions": 0,
162− "default": null,
163− "generated": null,
164− "identity": null,
165− "name": "display_name",
166− "entityType": "columns",
167− "schema": "public",
168− "table": "accounts"
169− },
170− {
171− "type": "bigint",
172− "typeSchema": null,
173− "notNull": true,
174− "dimensions": 0,
175− "default": null,
176− "generated": null,
177− "identity": null,
178− "name": "created_at",
179− "entityType": "columns",
180− "schema": "public",
181− "table": "accounts"
182− },
183− {
184− "type": "bigint",
185− "typeSchema": null,
186− "notNull": true,
187− "dimensions": 0,
188− "default": null,
189− "generated": null,
190− "identity": null,
191− "name": "updated_at",
192− "entityType": "columns",
193− "schema": "public",
194− "table": "accounts"
195− },
196− {
197− "type": "text",
198− "typeSchema": null,
199− "notNull": true,
200− "dimensions": 0,
201− "default": null,
202− "generated": null,
203− "identity": null,
204− "name": "id",
205− "entityType": "columns",
206− "schema": "public",
207− "table": "execution_interactions"
208− },
209− {
210− "type": "text",
211− "typeSchema": null,
212− "notNull": true,
213− "dimensions": 0,
214− "default": null,
215− "generated": null,
216− "identity": null,
217− "name": "execution_id",
218− "entityType": "columns",
219− "schema": "public",
220− "table": "execution_interactions"
221− },
222− {
223− "type": "text",
224− "typeSchema": null,
225− "notNull": true,
226− "dimensions": 0,
227− "default": null,
228− "generated": null,
229− "identity": null,
230− "name": "status",
231− "entityType": "columns",
232− "schema": "public",
233− "table": "execution_interactions"
234− },
235− {
236− "type": "text",
237− "typeSchema": null,
238− "notNull": true,
239− "dimensions": 0,
240− "default": null,
241− "generated": null,
242− "identity": null,
243− "name": "kind",
244− "entityType": "columns",
245− "schema": "public",
246− "table": "execution_interactions"
247− },
248− {
249− "type": "text",
250− "typeSchema": null,
251− "notNull": true,
252− "dimensions": 0,
253− "default": null,
254− "generated": null,
255− "identity": null,
256− "name": "payload_json",
257− "entityType": "columns",
258− "schema": "public",
259− "table": "execution_interactions"
260− },
261− {
262− "type": "text",
263− "typeSchema": null,
264− "notNull": false,
265− "dimensions": 0,
266− "default": null,
267− "generated": null,
268− "identity": null,
269− "name": "response_json",
270− "entityType": "columns",
271− "schema": "public",
272− "table": "execution_interactions"
273− },
274− {
275− "type": "bigint",
276− "typeSchema": null,
277− "notNull": true,
278− "dimensions": 0,
279− "default": null,
280− "generated": null,
281− "identity": null,
282− "name": "created_at",
283− "entityType": "columns",
284− "schema": "public",
285− "table": "execution_interactions"
286− },
287− {
288− "type": "bigint",
289− "typeSchema": null,
290− "notNull": true,
291− "dimensions": 0,
292− "default": null,
293− "generated": null,
294− "identity": null,
295− "name": "updated_at",
296− "entityType": "columns",
297− "schema": "public",
298− "table": "execution_interactions"
299− },
300− {
301− "type": "text",
302− "typeSchema": null,
303− "notNull": true,
304− "dimensions": 0,
305− "default": null,
306− "generated": null,
307− "identity": null,
308− "name": "id",
309− "entityType": "columns",
310− "schema": "public",
311− "table": "executions"
312− },
313− {
314− "type": "text",
315− "typeSchema": null,
316− "notNull": true,
317− "dimensions": 0,
318− "default": null,
319− "generated": null,
320− "identity": null,
321− "name": "workspace_id",
322− "entityType": "columns",
323− "schema": "public",
324− "table": "executions"
325− },
326− {
327− "type": "text",
328− "typeSchema": null,
329− "notNull": true,
330− "dimensions": 0,
331− "default": null,
332− "generated": null,
333− "identity": null,
334− "name": "created_by_account_id",
335− "entityType": "columns",
336− "schema": "public",
337− "table": "executions"
338− },
339− {
340− "type": "text",
341− "typeSchema": null,
342− "notNull": true,
343− "dimensions": 0,
344− "default": null,
345− "generated": null,
346− "identity": null,
347− "name": "status",
348− "entityType": "columns",
349− "schema": "public",
350− "table": "executions"
351− },
352− {
353− "type": "text",
354− "typeSchema": null,
355− "notNull": true,
356− "dimensions": 0,
357− "default": null,
358− "generated": null,
359− "identity": null,
360− "name": "code",
361− "entityType": "columns",
362− "schema": "public",
363− "table": "executions"
364− },
365− {
366− "type": "text",
367− "typeSchema": null,
368− "notNull": false,
369− "dimensions": 0,
370− "default": null,
371− "generated": null,
372− "identity": null,
373− "name": "result_json",
374− "entityType": "columns",
375− "schema": "public",
376− "table": "executions"
377− },
378− {
379− "type": "text",
380− "typeSchema": null,
381− "notNull": false,
382− "dimensions": 0,
383− "default": null,
384− "generated": null,
385− "identity": null,
386− "name": "error_text",
387− "entityType": "columns",
388− "schema": "public",
389− "table": "executions"
390− },
391− {
392− "type": "text",
393− "typeSchema": null,
394− "notNull": false,
395− "dimensions": 0,
396− "default": null,
397− "generated": null,
398− "identity": null,
399− "name": "logs_json",
400− "entityType": "columns",
401− "schema": "public",
402− "table": "executions"
403− },
404− {
405− "type": "bigint",
406− "typeSchema": null,
407− "notNull": false,
408− "dimensions": 0,
409− "default": null,
410− "generated": null,
411− "identity": null,
412− "name": "started_at",
413− "entityType": "columns",
414− "schema": "public",
415− "table": "executions"
416− },
417− {
418− "type": "bigint",
419− "typeSchema": null,
420− "notNull": false,
421− "dimensions": 0,
422− "default": null,
423− "generated": null,
424− "identity": null,
425− "name": "completed_at",
426− "entityType": "columns",
427− "schema": "public",
428− "table": "executions"
429− },
430− {
431− "type": "bigint",
432− "typeSchema": null,
433− "notNull": true,
434− "dimensions": 0,
435− "default": null,
436− "generated": null,
437− "identity": null,
438− "name": "created_at",
439− "entityType": "columns",
440− "schema": "public",
441− "table": "executions"
442− },
443− {
444− "type": "bigint",
445− "typeSchema": null,
446− "notNull": true,
447− "dimensions": 0,
448− "default": null,
449− "generated": null,
450− "identity": null,
451− "name": "updated_at",
452− "entityType": "columns",
453− "schema": "public",
454− "table": "executions"
455− },
456− {
457− "type": "text",
458− "typeSchema": null,
459− "notNull": true,
460− "dimensions": 0,
461− "default": null,
462− "generated": null,
463− "identity": null,
464− "name": "id",
465− "entityType": "columns",
466− "schema": "public",
467− "table": "local_installations"
468− },
469− {
470− "type": "text",
471− "typeSchema": null,
472− "notNull": true,
473− "dimensions": 0,
474− "default": null,
475− "generated": null,
476− "identity": null,
477− "name": "account_id",
478− "entityType": "columns",
479− "schema": "public",
480− "table": "local_installations"
481− },
482− {
483− "type": "text",
484− "typeSchema": null,
485− "notNull": true,
486− "dimensions": 0,
487− "default": null,
488− "generated": null,
489− "identity": null,
490− "name": "organization_id",
491− "entityType": "columns",
492− "schema": "public",
493− "table": "local_installations"
494− },
495− {
496− "type": "text",
497− "typeSchema": null,
498− "notNull": true,
499− "dimensions": 0,
500− "default": null,
501− "generated": null,
502− "identity": null,
503− "name": "workspace_id",
504− "entityType": "columns",
505− "schema": "public",
506− "table": "local_installations"
507− },
508− {
509− "type": "bigint",
510− "typeSchema": null,
511− "notNull": true,
512− "dimensions": 0,
513− "default": null,
514− "generated": null,
515− "identity": null,
516− "name": "created_at",
517− "entityType": "columns",
518− "schema": "public",
519− "table": "local_installations"
520− },
521− {
522− "type": "bigint",
523− "typeSchema": null,
524− "notNull": true,
525− "dimensions": 0,
526− "default": null,
527− "generated": null,
528− "identity": null,
529− "name": "updated_at",
530− "entityType": "columns",
531− "schema": "public",
532− "table": "local_installations"
533− },
534− {
535− "type": "text",
536− "typeSchema": null,
537− "notNull": true,
538− "dimensions": 0,
539− "default": null,
540− "generated": null,
541− "identity": null,
542− "name": "id",
543− "entityType": "columns",
544− "schema": "public",
545− "table": "organization_memberships"
546− },
547− {
548− "type": "text",
549− "typeSchema": null,
550− "notNull": true,
551− "dimensions": 0,
552− "default": null,
553− "generated": null,
554− "identity": null,
555− "name": "organization_id",
556− "entityType": "columns",
557− "schema": "public",
558− "table": "organization_memberships"
559− },
560− {
561− "type": "text",
562− "typeSchema": null,
563− "notNull": true,
564− "dimensions": 0,
565− "default": null,
566− "generated": null,
567− "identity": null,
568− "name": "account_id",
569− "entityType": "columns",
570− "schema": "public",
571− "table": "organization_memberships"
572− },
573− {
574− "type": "text",
575− "typeSchema": null,
576− "notNull": true,
577− "dimensions": 0,
578− "default": null,
579− "generated": null,
580− "identity": null,
581− "name": "role",
582− "entityType": "columns",
583− "schema": "public",
584− "table": "organization_memberships"
585− },
586− {
587− "type": "text",
588− "typeSchema": null,
589− "notNull": true,
590− "dimensions": 0,
591− "default": null,
592− "generated": null,
593− "identity": null,
594− "name": "status",
595− "entityType": "columns",
596− "schema": "public",
597− "table": "organization_memberships"
598− },
599− {
600− "type": "boolean",
601− "typeSchema": null,
602− "notNull": true,
603− "dimensions": 0,
604− "default": null,
605− "generated": null,
606− "identity": null,
607− "name": "billable",
608− "entityType": "columns",
609− "schema": "public",
610− "table": "organization_memberships"
611− },
612− {
613− "type": "text",
614− "typeSchema": null,
615− "notNull": false,
616− "dimensions": 0,
617− "default": null,
618− "generated": null,
619− "identity": null,
620− "name": "invited_by_account_id",
621− "entityType": "columns",
622− "schema": "public",
623− "table": "organization_memberships"
624− },
625− {
626− "type": "bigint",
627− "typeSchema": null,
628− "notNull": false,
629− "dimensions": 0,
630− "default": null,
631− "generated": null,
632− "identity": null,
633− "name": "joined_at",
634− "entityType": "columns",
635− "schema": "public",
636− "table": "organization_memberships"
637− },
638− {
639− "type": "bigint",
640− "typeSchema": null,
641− "notNull": true,
642− "dimensions": 0,
643− "default": null,
644− "generated": null,
645− "identity": null,
646− "name": "created_at",
647− "entityType": "columns",
648− "schema": "public",
649− "table": "organization_memberships"
650− },
651− {
652− "type": "bigint",
653− "typeSchema": null,
654− "notNull": true,
655− "dimensions": 0,
656− "default": null,
657− "generated": null,
658− "identity": null,
659− "name": "updated_at",
660− "entityType": "columns",
661− "schema": "public",
662− "table": "organization_memberships"
663− },
664− {
665− "type": "text",
666− "typeSchema": null,
667− "notNull": true,
668− "dimensions": 0,
669− "default": null,
670− "generated": null,
671− "identity": null,
672− "name": "id",
673− "entityType": "columns",
674− "schema": "public",
675− "table": "organizations"
676− },
677− {
678− "type": "text",
679− "typeSchema": null,
680− "notNull": true,
681− "dimensions": 0,
682− "default": null,
683− "generated": null,
684− "identity": null,
685− "name": "slug",
686− "entityType": "columns",
687− "schema": "public",
688− "table": "organizations"
689− },
690− {
691− "type": "text",
692− "typeSchema": null,
693− "notNull": true,
694− "dimensions": 0,
695− "default": null,
696− "generated": null,
697− "identity": null,
698− "name": "name",
699− "entityType": "columns",
700− "schema": "public",
701− "table": "organizations"
702− },
703− {
704− "type": "text",
705− "typeSchema": null,
706− "notNull": true,
707− "dimensions": 0,
708− "default": null,
709− "generated": null,
710− "identity": null,
711− "name": "status",
712− "entityType": "columns",
713− "schema": "public",
714− "table": "organizations"
715− },
716− {
717− "type": "text",
718− "typeSchema": null,
719− "notNull": false,
720− "dimensions": 0,
721− "default": null,
722− "generated": null,
723− "identity": null,
724− "name": "created_by_account_id",
725− "entityType": "columns",
726− "schema": "public",
727− "table": "organizations"
728− },
729− {
730− "type": "bigint",
731− "typeSchema": null,
732− "notNull": true,
733− "dimensions": 0,
734− "default": null,
735− "generated": null,
736− "identity": null,
737− "name": "created_at",
738− "entityType": "columns",
739− "schema": "public",
740− "table": "organizations"
741− },
742− {
743− "type": "bigint",
744− "typeSchema": null,
745− "notNull": true,
746− "dimensions": 0,
747− "default": null,
748− "generated": null,
749− "identity": null,
750− "name": "updated_at",
751− "entityType": "columns",
752− "schema": "public",
753− "table": "organizations"
754− },
755− {
756− "type": "text",
757− "typeSchema": null,
758− "notNull": true,
759− "dimensions": 0,
760− "default": null,
761− "generated": null,
762− "identity": null,
763− "name": "id",
764− "entityType": "columns",
765− "schema": "public",
766− "table": "policies"
767− },
768− {
769− "type": "text",
770− "typeSchema": null,
771− "notNull": true,
772− "dimensions": 0,
773− "default": null,
774− "generated": null,
775− "identity": null,
776− "name": "workspace_id",
777− "entityType": "columns",
778− "schema": "public",
779− "table": "policies"
780− },
781− {
782− "type": "text",
783− "typeSchema": null,
784− "notNull": false,
785− "dimensions": 0,
786− "default": null,
787− "generated": null,
788− "identity": null,
789− "name": "target_account_id",
790− "entityType": "columns",
791− "schema": "public",
792− "table": "policies"
793− },
794− {
795− "type": "text",
796− "typeSchema": null,
797− "notNull": false,
798− "dimensions": 0,
799− "default": null,
800− "generated": null,
801− "identity": null,
802− "name": "client_id",
803− "entityType": "columns",
804− "schema": "public",
805− "table": "policies"
806− },
807− {
808− "type": "text",
809− "typeSchema": null,
810− "notNull": true,
811− "dimensions": 0,
812− "default": null,
813− "generated": null,
814− "identity": null,
815− "name": "resource_type",
816− "entityType": "columns",
817− "schema": "public",
818− "table": "policies"
819− },
820− {
821− "type": "text",
822− "typeSchema": null,
823− "notNull": true,
824− "dimensions": 0,
825− "default": null,
826− "generated": null,
827− "identity": null,
828− "name": "resource_pattern",
829− "entityType": "columns",
830− "schema": "public",
831− "table": "policies"
832− },
833− {
834− "type": "text",
835− "typeSchema": null,
836− "notNull": true,
837− "dimensions": 0,
838− "default": null,
839− "generated": null,
840− "identity": null,
841− "name": "match_type",
842− "entityType": "columns",
843− "schema": "public",
844− "table": "policies"
845− },
846− {
847− "type": "text",
848− "typeSchema": null,
849− "notNull": true,
850− "dimensions": 0,
851− "default": null,
852− "generated": null,
853− "identity": null,
854− "name": "effect",
855− "entityType": "columns",
856− "schema": "public",
857− "table": "policies"
858− },
859− {
860− "type": "text",
861− "typeSchema": null,
862− "notNull": true,
863− "dimensions": 0,
864− "default": null,
865− "generated": null,
866− "identity": null,
867− "name": "approval_mode",
868− "entityType": "columns",
869− "schema": "public",
870− "table": "policies"
871− },
872− {
873− "type": "text",
874− "typeSchema": null,
875− "notNull": false,
876− "dimensions": 0,
877− "default": null,
878− "generated": null,
879− "identity": null,
880− "name": "argument_conditions_json",
881− "entityType": "columns",
882− "schema": "public",
883− "table": "policies"
884− },
885− {
886− "type": "bigint",
887− "typeSchema": null,
888− "notNull": true,
889− "dimensions": 0,
890− "default": null,
891− "generated": null,
892− "identity": null,
893− "name": "priority",
894− "entityType": "columns",
895− "schema": "public",
896− "table": "policies"
897− },
898− {
899− "type": "boolean",
900− "typeSchema": null,
901− "notNull": true,
902− "dimensions": 0,
903− "default": null,
904− "generated": null,
905− "identity": null,
906− "name": "enabled",
907− "entityType": "columns",
908− "schema": "public",
909− "table": "policies"
910− },
911− {
912− "type": "bigint",
913− "typeSchema": null,
914− "notNull": true,
915− "dimensions": 0,
916− "default": null,
917− "generated": null,
918− "identity": null,
919− "name": "created_at",
920− "entityType": "columns",
921− "schema": "public",
922− "table": "policies"
923− },
924− {
925− "type": "bigint",
926− "typeSchema": null,
927− "notNull": true,
928− "dimensions": 0,
929− "default": null,
930− "generated": null,
931− "identity": null,
932− "name": "updated_at",
933− "entityType": "columns",
934− "schema": "public",
935− "table": "policies"
936− },
937− {
938− "type": "text",
939− "typeSchema": null,
940− "notNull": true,
941− "dimensions": 0,
942− "default": null,
943− "generated": null,
944− "identity": null,
945− "name": "id",
946− "entityType": "columns",
947− "schema": "public",
948− "table": "secret_materials"
949− },
950− {
951− "type": "text",
952− "typeSchema": null,
953− "notNull": true,
954− "dimensions": 0,
955− "default": null,
956− "generated": null,
957− "identity": null,
958− "name": "purpose",
959− "entityType": "columns",
960− "schema": "public",
961− "table": "secret_materials"
962− },
963− {
964− "type": "text",
965− "typeSchema": null,
966− "notNull": true,
967− "dimensions": 0,
968− "default": null,
969− "generated": null,
970− "identity": null,
971− "name": "value",
972− "entityType": "columns",
973− "schema": "public",
974− "table": "secret_materials"
975− },
976− {
977− "type": "bigint",
978− "typeSchema": null,
979− "notNull": true,
980− "dimensions": 0,
981− "default": null,
982− "generated": null,
983− "identity": null,
984− "name": "created_at",
985− "entityType": "columns",
986− "schema": "public",
987− "table": "secret_materials"
988− },
989− {
990− "type": "bigint",
991− "typeSchema": null,
992− "notNull": true,
993− "dimensions": 0,
994− "default": null,
995− "generated": null,
996− "identity": null,
997− "name": "updated_at",
998− "entityType": "columns",
999− "schema": "public",
1000− "table": "secret_materials"
1001− },
1002− {
1003− "type": "text",
1004− "typeSchema": null,
1005− "notNull": true,
1006− "dimensions": 0,
1007− "default": null,
1008− "generated": null,
1009− "identity": null,
1010− "name": "id",
1011− "entityType": "columns",
1012− "schema": "public",
1013− "table": "source_auth_sessions"
1014− },
1015− {
1016− "type": "text",
1017− "typeSchema": null,
1018− "notNull": true,
1019− "dimensions": 0,
1020− "default": null,
1021− "generated": null,
1022− "identity": null,
1023− "name": "workspace_id",
1024− "entityType": "columns",
1025− "schema": "public",
1026− "table": "source_auth_sessions"
1027− },
1028− {
1029− "type": "text",
1030− "typeSchema": null,
1031− "notNull": true,
1032− "dimensions": 0,
1033− "default": null,
1034− "generated": null,
1035− "identity": null,
1036− "name": "source_id",
1037− "entityType": "columns",
1038− "schema": "public",
1039− "table": "source_auth_sessions"
1040− },
1041− {
1042− "type": "text",
1043− "typeSchema": null,
1044− "notNull": false,
1045− "dimensions": 0,
1046− "default": null,
1047− "generated": null,
1048− "identity": null,
1049− "name": "execution_id",
1050− "entityType": "columns",
1051− "schema": "public",
1052− "table": "source_auth_sessions"
1053− },
1054− {
1055− "type": "text",
1056− "typeSchema": null,
1057− "notNull": false,
1058− "dimensions": 0,
1059− "default": null,
1060− "generated": null,
1061− "identity": null,
1062− "name": "interaction_id",
1063− "entityType": "columns",
1064− "schema": "public",
1065− "table": "source_auth_sessions"
1066− },
1067− {
1068− "type": "text",
1069− "typeSchema": null,
1070− "notNull": true,
1071− "dimensions": 0,
1072− "default": null,
1073− "generated": null,
1074− "identity": null,
1075− "name": "strategy",
1076− "entityType": "columns",
1077− "schema": "public",
1078− "table": "source_auth_sessions"
1079− },
1080− {
1081− "type": "text",
1082− "typeSchema": null,
1083− "notNull": true,
1084− "dimensions": 0,
1085− "default": null,
1086− "generated": null,
1087− "identity": null,
1088− "name": "status",
1089− "entityType": "columns",
1090− "schema": "public",
1091− "table": "source_auth_sessions"
1092− },
1093− {
1094− "type": "text",
1095− "typeSchema": null,
1096− "notNull": true,
1097− "dimensions": 0,
1098− "default": null,
1099− "generated": null,
1100− "identity": null,
1101− "name": "endpoint",
1102− "entityType": "columns",
1103− "schema": "public",
1104− "table": "source_auth_sessions"
1105− },
1106− {
1107− "type": "text",
1108− "typeSchema": null,
1109− "notNull": true,
1110− "dimensions": 0,
1111− "default": null,
1112− "generated": null,
1113− "identity": null,
1114− "name": "state",
1115− "entityType": "columns",
1116− "schema": "public",
1117− "table": "source_auth_sessions"
1118− },
1119− {
1120− "type": "text",
1121− "typeSchema": null,
1122− "notNull": true,
1123− "dimensions": 0,
1124− "default": null,
1125− "generated": null,
1126− "identity": null,
1127− "name": "redirect_uri",
1128− "entityType": "columns",
1129− "schema": "public",
1130− "table": "source_auth_sessions"
1131− },
1132− {
1133− "type": "text",
1134− "typeSchema": null,
1135− "notNull": false,
1136− "dimensions": 0,
1137− "default": null,
1138− "generated": null,
1139− "identity": null,
1140− "name": "scope",
1141− "entityType": "columns",
1142− "schema": "public",
1143− "table": "source_auth_sessions"
1144− },
1145− {
1146− "type": "text",
1147− "typeSchema": null,
1148− "notNull": false,
1149− "dimensions": 0,
1150− "default": null,
1151− "generated": null,
1152− "identity": null,
1153− "name": "resource_metadata_url",
1154− "entityType": "columns",
1155− "schema": "public",
1156− "table": "source_auth_sessions"
1157− },
1158− {
1159− "type": "text",
1160− "typeSchema": null,
1161− "notNull": false,
1162− "dimensions": 0,
1163− "default": null,
1164− "generated": null,
1165− "identity": null,
1166− "name": "authorization_server_url",
1167− "entityType": "columns",
1168− "schema": "public",
1169− "table": "source_auth_sessions"
1170− },
1171− {
1172− "type": "text",
1173− "typeSchema": null,
1174− "notNull": false,
1175− "dimensions": 0,
1176− "default": null,
1177− "generated": null,
1178− "identity": null,
1179− "name": "resource_metadata_json",
1180− "entityType": "columns",
1181− "schema": "public",
1182− "table": "source_auth_sessions"
1183− },
1184− {
1185− "type": "text",
1186− "typeSchema": null,
1187− "notNull": false,
1188− "dimensions": 0,
1189− "default": null,
1190− "generated": null,
1191− "identity": null,
1192− "name": "authorization_server_metadata_json",
1193− "entityType": "columns",
1194− "schema": "public",
1195− "table": "source_auth_sessions"
1196− },
1197− {
1198− "type": "text",
1199− "typeSchema": null,
1200− "notNull": false,
1201− "dimensions": 0,
1202− "default": null,
1203− "generated": null,
1204− "identity": null,
1205− "name": "client_information_json",
1206− "entityType": "columns",
1207− "schema": "public",
1208− "table": "source_auth_sessions"
1209− },
1210− {
1211− "type": "text",
1212− "typeSchema": null,
1213− "notNull": false,
1214− "dimensions": 0,
1215− "default": null,
1216− "generated": null,
1217− "identity": null,
1218− "name": "code_verifier",
1219− "entityType": "columns",
1220− "schema": "public",
1221− "table": "source_auth_sessions"
1222− },
1223− {
1224− "type": "text",
1225− "typeSchema": null,
1226− "notNull": false,
1227− "dimensions": 0,
1228− "default": null,
1229− "generated": null,
1230− "identity": null,
1231− "name": "authorization_url",
1232− "entityType": "columns",
1233− "schema": "public",
1234− "table": "source_auth_sessions"
1235− },
1236− {
1237− "type": "text",
1238− "typeSchema": null,
1239− "notNull": false,
1240− "dimensions": 0,
1241− "default": null,
1242− "generated": null,
1243− "identity": null,
1244− "name": "error_text",
1245− "entityType": "columns",
1246− "schema": "public",
1247− "table": "source_auth_sessions"
1248− },
1249− {
1250− "type": "bigint",
1251− "typeSchema": null,
1252− "notNull": false,
1253− "dimensions": 0,
1254− "default": null,
1255− "generated": null,
1256− "identity": null,
1257− "name": "completed_at",
1258− "entityType": "columns",
1259− "schema": "public",
1260− "table": "source_auth_sessions"
1261− },
1262− {
1263− "type": "bigint",
1264− "typeSchema": null,
1265− "notNull": true,
1266− "dimensions": 0,
1267− "default": null,
1268− "generated": null,
1269− "identity": null,
1270− "name": "created_at",
1271− "entityType": "columns",
1272− "schema": "public",
1273− "table": "source_auth_sessions"
1274− },
1275− {
1276− "type": "bigint",
1277− "typeSchema": null,
1278− "notNull": true,
1279− "dimensions": 0,
1280− "default": null,
1281− "generated": null,
1282− "identity": null,
1283− "name": "updated_at",
1284− "entityType": "columns",
1285− "schema": "public",
1286− "table": "source_auth_sessions"
1287− },
1288− {
1289− "type": "text",
1290− "typeSchema": null,
1291− "notNull": true,
1292− "dimensions": 0,
1293− "default": null,
1294− "generated": null,
1295− "identity": null,
1296− "name": "workspace_id",
1297− "entityType": "columns",
1298− "schema": "public",
1299− "table": "source_credential_bindings"
1300− },
1301− {
1302− "type": "text",
1303− "typeSchema": null,
1304− "notNull": true,
1305− "dimensions": 0,
1306− "default": null,
1307− "generated": null,
1308− "identity": null,
1309− "name": "source_id",
1310− "entityType": "columns",
1311− "schema": "public",
1312− "table": "source_credential_bindings"
1313− },
1314− {
1315− "type": "text",
1316− "typeSchema": null,
1317− "notNull": false,
1318− "dimensions": 0,
1319− "default": null,
1320− "generated": null,
1321− "identity": null,
1322− "name": "token_provider_id",
1323− "entityType": "columns",
1324− "schema": "public",
1325− "table": "source_credential_bindings"
1326− },
1327− {
1328− "type": "text",
1329− "typeSchema": null,
1330− "notNull": false,
1331− "dimensions": 0,
1332− "default": null,
1333− "generated": null,
1334− "identity": null,
1335− "name": "token_handle",
1336− "entityType": "columns",
1337− "schema": "public",
1338− "table": "source_credential_bindings"
1339− },
1340− {
1341− "type": "text",
1342− "typeSchema": null,
1343− "notNull": false,
1344− "dimensions": 0,
1345− "default": null,
1346− "generated": null,
1347− "identity": null,
1348− "name": "refresh_token_provider_id",
1349− "entityType": "columns",
1350− "schema": "public",
1351− "table": "source_credential_bindings"
1352− },
1353− {
1354− "type": "text",
1355− "typeSchema": null,
1356− "notNull": false,
1357− "dimensions": 0,
1358− "default": null,
1359− "generated": null,
1360− "identity": null,
1361− "name": "refresh_token_handle",
1362− "entityType": "columns",
1363− "schema": "public",
1364− "table": "source_credential_bindings"
1365− },
1366− {
1367− "type": "bigint",
1368− "typeSchema": null,
1369− "notNull": true,
1370− "dimensions": 0,
1371− "default": null,
1372− "generated": null,
1373− "identity": null,
1374− "name": "created_at",
1375− "entityType": "columns",
1376− "schema": "public",
1377− "table": "source_credential_bindings"
1378− },
1379− {
1380− "type": "bigint",
1381− "typeSchema": null,
1382− "notNull": true,
1383− "dimensions": 0,
1384− "default": null,
1385− "generated": null,
1386− "identity": null,
1387− "name": "updated_at",
1388− "entityType": "columns",
1389− "schema": "public",
1390− "table": "source_credential_bindings"
1391− },
1392− {
1393− "type": "text",
1394− "typeSchema": null,
1395− "notNull": true,
1396− "dimensions": 0,
1397− "default": null,
1398− "generated": null,
1399− "identity": null,
1400− "name": "workspace_id",
1401− "entityType": "columns",
1402− "schema": "public",
1403− "table": "sources"
1404− },
1405− {
1406− "type": "text",
1407− "typeSchema": null,
1408− "notNull": true,
1409− "dimensions": 0,
1410− "default": null,
1411− "generated": null,
1412− "identity": null,
1413− "name": "source_id",
1414− "entityType": "columns",
1415− "schema": "public",
1416− "table": "sources"
1417− },
1418− {
1419− "type": "text",
1420− "typeSchema": null,
1421− "notNull": true,
1422− "dimensions": 0,
1423− "default": null,
1424− "generated": null,
1425− "identity": null,
1426− "name": "name",
1427− "entityType": "columns",
1428− "schema": "public",
1429− "table": "sources"
1430− },
1431− {
1432− "type": "text",
1433− "typeSchema": null,
1434− "notNull": true,
1435− "dimensions": 0,
1436− "default": null,
1437− "generated": null,
1438− "identity": null,
1439− "name": "kind",
1440− "entityType": "columns",
1441− "schema": "public",
1442− "table": "sources"
1443− },
1444− {
1445− "type": "text",
1446− "typeSchema": null,
1447− "notNull": true,
1448− "dimensions": 0,
1449− "default": null,
1450− "generated": null,
1451− "identity": null,
1452− "name": "endpoint",
1453− "entityType": "columns",
1454− "schema": "public",
1455− "table": "sources"
1456− },
1457− {
1458− "type": "text",
1459− "typeSchema": null,
1460− "notNull": true,
1461− "dimensions": 0,
1462− "default": null,
1463− "generated": null,
1464− "identity": null,
1465− "name": "status",
1466− "entityType": "columns",
1467− "schema": "public",
1468− "table": "sources"
1469− },
1470− {
1471− "type": "boolean",
1472− "typeSchema": null,
1473− "notNull": true,
1474− "dimensions": 0,
1475− "default": null,
1476− "generated": null,
1477− "identity": null,
1478− "name": "enabled",
1479− "entityType": "columns",
1480− "schema": "public",
1481− "table": "sources"
1482− },
1483− {
1484− "type": "text",
1485− "typeSchema": null,
1486− "notNull": false,
1487− "dimensions": 0,
1488− "default": null,
1489− "generated": null,
1490− "identity": null,
1491− "name": "namespace",
1492− "entityType": "columns",
1493− "schema": "public",
1494− "table": "sources"
1495− },
1496− {
1497− "type": "text",
1498− "typeSchema": null,
1499− "notNull": false,
1500− "dimensions": 0,
1501− "default": null,
1502− "generated": null,
1503− "identity": null,
1504− "name": "transport",
1505− "entityType": "columns",
1506− "schema": "public",
1507− "table": "sources"
1508− },
1509− {
1510− "type": "text",
1511− "typeSchema": null,
1512− "notNull": false,
1513− "dimensions": 0,
1514− "default": null,
1515− "generated": null,
1516− "identity": null,
1517− "name": "query_params_json",
1518− "entityType": "columns",
1519− "schema": "public",
1520− "table": "sources"
1521− },
1522− {
1523− "type": "text",
1524− "typeSchema": null,
1525− "notNull": false,
1526− "dimensions": 0,
1527− "default": null,
1528− "generated": null,
1529− "identity": null,
1530− "name": "headers_json",
1531− "entityType": "columns",
1532− "schema": "public",
1533− "table": "sources"
1534− },
1535− {
1536− "type": "text",
1537− "typeSchema": null,
1538− "notNull": false,
1539− "dimensions": 0,
1540− "default": null,
1541− "generated": null,
1542− "identity": null,
1543− "name": "spec_url",
1544− "entityType": "columns",
1545− "schema": "public",
1546− "table": "sources"
1547− },
1548− {
1549− "type": "text",
1550− "typeSchema": null,
1551− "notNull": false,
1552− "dimensions": 0,
1553− "default": null,
1554− "generated": null,
1555− "identity": null,
1556− "name": "default_headers_json",
1557− "entityType": "columns",
1558− "schema": "public",
1559− "table": "sources"
1560− },
1561− {
1562− "type": "text",
1563− "typeSchema": null,
1564− "notNull": true,
1565− "dimensions": 0,
1566− "default": null,
1567− "generated": null,
1568− "identity": null,
1569− "name": "auth_kind",
1570− "entityType": "columns",
1571− "schema": "public",
1572− "table": "sources"
1573− },
1574− {
1575− "type": "text",
1576− "typeSchema": null,
1577− "notNull": false,
1578− "dimensions": 0,
1579− "default": null,
1580− "generated": null,
1581− "identity": null,
1582− "name": "auth_header_name",
1583− "entityType": "columns",
1584− "schema": "public",
1585− "table": "sources"
1586− },
1587− {
1588− "type": "text",
1589− "typeSchema": null,
1590− "notNull": false,
1591− "dimensions": 0,
1592− "default": null,
1593− "generated": null,
1594− "identity": null,
1595− "name": "auth_prefix",
1596− "entityType": "columns",
1597− "schema": "public",
1598− "table": "sources"
1599− },
1600− {
1601− "type": "text",
1602− "typeSchema": null,
1603− "notNull": false,
1604− "dimensions": 0,
1605− "default": null,
1606− "generated": null,
1607− "identity": null,
1608− "name": "source_hash",
1609− "entityType": "columns",
1610− "schema": "public",
1611− "table": "sources"
1612− },
1613− {
1614− "type": "text",
1615− "typeSchema": null,
1616− "notNull": false,
1617− "dimensions": 0,
1618− "default": null,
1619− "generated": null,
1620− "identity": null,
1621− "name": "last_error",
1622− "entityType": "columns",
1623− "schema": "public",
1624− "table": "sources"
1625− },
1626− {
1627− "type": "bigint",
1628− "typeSchema": null,
1629− "notNull": true,
1630− "dimensions": 0,
1631− "default": null,
1632− "generated": null,
1633− "identity": null,
1634− "name": "created_at",
1635− "entityType": "columns",
1636− "schema": "public",
1637− "table": "sources"
1638− },
1639− {
1640− "type": "bigint",
1641− "typeSchema": null,
1642− "notNull": true,
1643− "dimensions": 0,
1644− "default": null,
1645− "generated": null,
1646− "identity": null,
1647− "name": "updated_at",
1648− "entityType": "columns",
1649− "schema": "public",
1650− "table": "sources"
1651− },
1652− {
1653− "type": "text",
1654− "typeSchema": null,
1655− "notNull": true,
1656− "dimensions": 0,
1657− "default": null,
1658− "generated": null,
1659− "identity": null,
1660− "name": "workspace_id",
1661− "entityType": "columns",
1662− "schema": "public",
1663− "table": "tool_artifact_parameters"
1664− },
1665− {
1666− "type": "text",
1667− "typeSchema": null,
1668− "notNull": true,
1669− "dimensions": 0,
1670− "default": null,
1671− "generated": null,
1672− "identity": null,
1673− "name": "path",
1674− "entityType": "columns",
1675− "schema": "public",
1676− "table": "tool_artifact_parameters"
1677− },
1678− {
1679− "type": "bigint",
1680− "typeSchema": null,
1681− "notNull": true,
1682− "dimensions": 0,
1683− "default": null,
1684− "generated": null,
1685− "identity": null,
1686− "name": "position",
1687− "entityType": "columns",
1688− "schema": "public",
1689− "table": "tool_artifact_parameters"
1690− },
1691− {
1692− "type": "text",
1693− "typeSchema": null,
1694− "notNull": true,
1695− "dimensions": 0,
1696− "default": null,
1697− "generated": null,
1698− "identity": null,
1699− "name": "name",
1700− "entityType": "columns",
1701− "schema": "public",
1702− "table": "tool_artifact_parameters"
1703− },
1704− {
1705− "type": "text",
1706− "typeSchema": null,
1707− "notNull": true,
1708− "dimensions": 0,
1709− "default": null,
1710− "generated": null,
1711− "identity": null,
1712− "name": "location",
1713− "entityType": "columns",
1714− "schema": "public",
1715− "table": "tool_artifact_parameters"
1716− },
1717− {
1718− "type": "boolean",
1719− "typeSchema": null,
1720− "notNull": true,
1721− "dimensions": 0,
1722− "default": null,
1723− "generated": null,
1724− "identity": null,
1725− "name": "required",
1726− "entityType": "columns",
1727− "schema": "public",
1728− "table": "tool_artifact_parameters"
1729− },
1730− {
1731− "type": "text",
1732− "typeSchema": null,
1733− "notNull": true,
1734− "dimensions": 0,
1735− "default": null,
1736− "generated": null,
1737− "identity": null,
1738− "name": "workspace_id",
1739− "entityType": "columns",
1740− "schema": "public",
1741− "table": "tool_artifact_ref_hint_keys"
1742− },
1743− {
1744− "type": "text",
1745− "typeSchema": null,
1746− "notNull": true,
1747− "dimensions": 0,
1748− "default": null,
1749− "generated": null,
1750− "identity": null,
1751− "name": "path",
1752− "entityType": "columns",
1753− "schema": "public",
1754− "table": "tool_artifact_ref_hint_keys"
1755− },
1756− {
1757− "type": "bigint",
1758− "typeSchema": null,
1759− "notNull": true,
1760− "dimensions": 0,
1761− "default": null,
1762− "generated": null,
1763− "identity": null,
1764− "name": "position",
1765− "entityType": "columns",
1766− "schema": "public",
1767− "table": "tool_artifact_ref_hint_keys"
1768− },
1769− {
1770− "type": "text",
1771− "typeSchema": null,
1772− "notNull": true,
1773− "dimensions": 0,
1774− "default": null,
1775− "generated": null,
1776− "identity": null,
1777− "name": "ref_hint_key",
1778− "entityType": "columns",
1779− "schema": "public",
1780− "table": "tool_artifact_ref_hint_keys"
1781− },
1782− {
1783− "type": "text",
1784− "typeSchema": null,
1785− "notNull": true,
1786− "dimensions": 0,
1787− "default": null,
1788− "generated": null,
1789− "identity": null,
1790− "name": "workspace_id",
1791− "entityType": "columns",
1792− "schema": "public",
1793− "table": "tool_artifact_request_body_content_types"
1794− },
1795− {
1796− "type": "text",
1797− "typeSchema": null,
1798− "notNull": true,
1799− "dimensions": 0,
1800− "default": null,
1801− "generated": null,
1802− "identity": null,
1803− "name": "path",
1804− "entityType": "columns",
1805− "schema": "public",
1806− "table": "tool_artifact_request_body_content_types"
1807− },
1808− {
1809− "type": "bigint",
1810− "typeSchema": null,
1811− "notNull": true,
1812− "dimensions": 0,
1813− "default": null,
1814− "generated": null,
1815− "identity": null,
1816− "name": "position",
1817− "entityType": "columns",
1818− "schema": "public",
1819− "table": "tool_artifact_request_body_content_types"
1820− },
1821− {
1822− "type": "text",
1823− "typeSchema": null,
1824− "notNull": true,
1825− "dimensions": 0,
1826− "default": null,
1827− "generated": null,
1828− "identity": null,
1829− "name": "content_type",
1830− "entityType": "columns",
1831− "schema": "public",
1832− "table": "tool_artifact_request_body_content_types"
1833− },
1834− {
1835− "type": "text",
1836− "typeSchema": null,
1837− "notNull": true,
1838− "dimensions": 0,
1839− "default": null,
1840− "generated": null,
1841− "identity": null,
1842− "name": "workspace_id",
1843− "entityType": "columns",
1844− "schema": "public",
1845− "table": "tool_artifacts"
1846− },
1847− {
1848− "type": "text",
1849− "typeSchema": null,
1850− "notNull": true,
1851− "dimensions": 0,
1852− "default": null,
1853− "generated": null,
1854− "identity": null,
1855− "name": "path",
1856− "entityType": "columns",
1857− "schema": "public",
1858− "table": "tool_artifacts"
1859− },
1860− {
1861− "type": "text",
1862− "typeSchema": null,
1863− "notNull": true,
1864− "dimensions": 0,
1865− "default": null,
1866− "generated": null,
1867− "identity": null,
1868− "name": "tool_id",
1869− "entityType": "columns",
1870− "schema": "public",
1871− "table": "tool_artifacts"
1872− },
1873− {
1874− "type": "text",
1875− "typeSchema": null,
1876− "notNull": true,
1877− "dimensions": 0,
1878− "default": null,
1879− "generated": null,
1880− "identity": null,
1881− "name": "source_id",
1882− "entityType": "columns",
1883− "schema": "public",
1884− "table": "tool_artifacts"
1885− },
1886− {
1887− "type": "text",
1888− "typeSchema": null,
1889− "notNull": false,
1890− "dimensions": 0,
1891− "default": null,
1892− "generated": null,
1893− "identity": null,
1894− "name": "title",
1895− "entityType": "columns",
1896− "schema": "public",
1897− "table": "tool_artifacts"
1898− },
1899− {
1900− "type": "text",
1901− "typeSchema": null,
1902− "notNull": false,
1903− "dimensions": 0,
1904− "default": null,
1905− "generated": null,
1906− "identity": null,
1907− "name": "description",
1908− "entityType": "columns",
1909− "schema": "public",
1910− "table": "tool_artifacts"
1911− },
1912− {
1913− "type": "text",
1914− "typeSchema": null,
1915− "notNull": true,
1916− "dimensions": 0,
1917− "default": null,
1918− "generated": null,
1919− "identity": null,
1920− "name": "search_namespace",
1921− "entityType": "columns",
1922− "schema": "public",
1923− "table": "tool_artifacts"
1924− },
1925− {
1926− "type": "text",
1927− "typeSchema": null,
1928− "notNull": true,
1929− "dimensions": 0,
1930− "default": null,
1931− "generated": null,
1932− "identity": null,
1933− "name": "search_text",
1934− "entityType": "columns",
1935− "schema": "public",
1936− "table": "tool_artifacts"
1937− },
1938− {
1939− "type": "text",
1940− "typeSchema": null,
1941− "notNull": false,
1942− "dimensions": 0,
1943− "default": null,
1944− "generated": null,
1945− "identity": null,
1946− "name": "input_schema_json",
1947− "entityType": "columns",
1948− "schema": "public",
1949− "table": "tool_artifacts"
1950− },
1951− {
1952− "type": "text",
1953− "typeSchema": null,
1954− "notNull": false,
1955− "dimensions": 0,
1956− "default": null,
1957− "generated": null,
1958− "identity": null,
1959− "name": "output_schema_json",
1960− "entityType": "columns",
1961− "schema": "public",
1962− "table": "tool_artifacts"
1963− },
1964− {
1965− "type": "text",
1966− "typeSchema": null,
1967− "notNull": true,
1968− "dimensions": 0,
1969− "default": null,
1970− "generated": null,
1971− "identity": null,
1972− "name": "provider_kind",
1973− "entityType": "columns",
1974− "schema": "public",
1975− "table": "tool_artifacts"
1976− },
1977− {
1978− "type": "text",
1979− "typeSchema": null,
1980− "notNull": false,
1981− "dimensions": 0,
1982− "default": null,
1983− "generated": null,
1984− "identity": null,
1985− "name": "mcp_tool_name",
1986− "entityType": "columns",
1987− "schema": "public",
1988− "table": "tool_artifacts"
1989− },
1990− {
1991− "type": "text",
1992− "typeSchema": null,
1993− "notNull": false,
1994− "dimensions": 0,
1995− "default": null,
1996− "generated": null,
1997− "identity": null,
1998− "name": "openapi_method",
1999− "entityType": "columns",
2000− "schema": "public",
2001− "table": "tool_artifacts"
2002− },
2003− {
2004− "type": "text",
2005− "typeSchema": null,
2006− "notNull": false,
2007− "dimensions": 0,
2008− "default": null,
2009− "generated": null,
2010− "identity": null,
2011− "name": "openapi_path_template",
2012− "entityType": "columns",
2013− "schema": "public",
2014− "table": "tool_artifacts"
2015− },
2016− {
2017− "type": "text",
2018− "typeSchema": null,
2019− "notNull": false,
2020− "dimensions": 0,
2021− "default": null,
2022− "generated": null,
2023− "identity": null,
2024− "name": "openapi_operation_hash",
2025− "entityType": "columns",
2026− "schema": "public",
2027− "table": "tool_artifacts"
2028− },
2029− {
2030− "type": "boolean",
2031− "typeSchema": null,
2032− "notNull": false,
2033− "dimensions": 0,
2034− "default": null,
2035− "generated": null,
2036− "identity": null,
2037− "name": "openapi_request_body_required",
2038− "entityType": "columns",
2039− "schema": "public",
2040− "table": "tool_artifacts"
2041− },
2042− {
2043− "type": "bigint",
2044− "typeSchema": null,
2045− "notNull": true,
2046− "dimensions": 0,
2047− "default": null,
2048− "generated": null,
2049− "identity": null,
2050− "name": "created_at",
2051− "entityType": "columns",
2052− "schema": "public",
2053− "table": "tool_artifacts"
2054− },
2055− {
2056− "type": "bigint",
2057− "typeSchema": null,
2058− "notNull": true,
2059− "dimensions": 0,
2060− "default": null,
2061− "generated": null,
2062− "identity": null,
2063− "name": "updated_at",
2064− "entityType": "columns",
2065− "schema": "public",
2066− "table": "tool_artifacts"
2067− },
2068− {
2069− "type": "text",
2070− "typeSchema": null,
2071− "notNull": true,
2072− "dimensions": 0,
2073− "default": null,
2074− "generated": null,
2075− "identity": null,
2076− "name": "id",
2077− "entityType": "columns",
2078− "schema": "public",
2079− "table": "workspaces"
2080− },
2081− {
2082− "type": "text",
2083− "typeSchema": null,
2084− "notNull": true,
2085− "dimensions": 0,
2086− "default": null,
2087− "generated": null,
2088− "identity": null,
2089− "name": "organization_id",
2090− "entityType": "columns",
2091− "schema": "public",
2092− "table": "workspaces"
2093− },
2094− {
2095− "type": "text",
2096− "typeSchema": null,
2097− "notNull": true,
2098− "dimensions": 0,
2099− "default": null,
2100− "generated": null,
2101− "identity": null,
2102− "name": "name",
2103− "entityType": "columns",
2104− "schema": "public",
2105− "table": "workspaces"
2106− },
2107− {
2108− "type": "text",
2109− "typeSchema": null,
2110− "notNull": false,
2111− "dimensions": 0,
2112− "default": null,
2113− "generated": null,
2114− "identity": null,
2115− "name": "created_by_account_id",
2116− "entityType": "columns",
2117− "schema": "public",
2118− "table": "workspaces"
2119− },
2120− {
2121− "type": "bigint",
2122− "typeSchema": null,
2123− "notNull": true,
2124− "dimensions": 0,
2125− "default": null,
2126− "generated": null,
2127− "identity": null,
2128− "name": "created_at",
2129− "entityType": "columns",
2130− "schema": "public",
2131− "table": "workspaces"
2132− },
2133− {
2134− "type": "bigint",
2135− "typeSchema": null,
2136− "notNull": true,
2137− "dimensions": 0,
2138− "default": null,
2139− "generated": null,
2140− "identity": null,
2141− "name": "updated_at",
2142− "entityType": "columns",
2143− "schema": "public",
2144− "table": "workspaces"
2145− },
2146− {
2147− "nameExplicit": true,
2148− "columns": [
2149− {
2150− "value": "provider",
2151− "isExpression": false,
2152− "asc": true,
2153− "nullsFirst": false,
2154− "opclass": null
2155− },
2156− {
2157− "value": "subject",
2158− "isExpression": false,
2159− "asc": true,
2160− "nullsFirst": false,
2161− "opclass": null
2162− }
2163− ],
2164− "isUnique": true,
2165− "where": null,
2166− "with": "",
2167− "method": "btree",
2168− "concurrently": false,
2169− "name": "accounts_provider_subject_idx",
2170− "entityType": "indexes",
2171− "schema": "public",
2172− "table": "accounts"
2173− },
2174− {
2175− "nameExplicit": true,
2176− "columns": [
2177− {
2178− "value": "updated_at",
2179− "isExpression": false,
2180− "asc": true,
2181− "nullsFirst": false,
2182− "opclass": null
2183− },
2184− {
2185− "value": "id",
2186− "isExpression": false,
2187− "asc": true,
2188− "nullsFirst": false,
2189− "opclass": null
2190− }
2191− ],
2192− "isUnique": false,
2193− "where": null,
2194− "with": "",
2195− "method": "btree",
2196− "concurrently": false,
2197− "name": "accounts_updated_idx",
2198− "entityType": "indexes",
2199− "schema": "public",
2200− "table": "accounts"
2201− },
2202− {
2203− "nameExplicit": true,
2204− "columns": [
2205− {
2206− "value": "execution_id",
2207− "isExpression": false,
2208− "asc": true,
2209− "nullsFirst": false,
2210− "opclass": null
2211− },
2212− {
2213− "value": "updated_at",
2214− "isExpression": false,
2215− "asc": true,
2216− "nullsFirst": false,
2217− "opclass": null
2218− },
2219− {
2220− "value": "id",
2221− "isExpression": false,
2222− "asc": true,
2223− "nullsFirst": false,
2224− "opclass": null
2225− }
2226− ],
2227− "isUnique": false,
2228− "where": null,
2229− "with": "",
2230− "method": "btree",
2231− "concurrently": false,
2232− "name": "execution_interactions_execution_idx",
2233− "entityType": "indexes",
2234− "schema": "public",
2235− "table": "execution_interactions"
2236− },
2237− {
2238− "nameExplicit": true,
2239− "columns": [
2240− {
2241− "value": "workspace_id",
2242− "isExpression": false,
2243− "asc": true,
2244− "nullsFirst": false,
2245− "opclass": null
2246− },
2247− {
2248− "value": "updated_at",
2249− "isExpression": false,
2250− "asc": true,
2251− "nullsFirst": false,
2252− "opclass": null
2253− },
2254− {
2255− "value": "id",
2256− "isExpression": false,
2257− "asc": true,
2258− "nullsFirst": false,
2259− "opclass": null
2260− }
2261− ],
2262− "isUnique": false,
2263− "where": null,
2264− "with": "",
2265− "method": "btree",
2266− "concurrently": false,
2267− "name": "executions_workspace_idx",
2268− "entityType": "indexes",
2269− "schema": "public",
2270− "table": "executions"
2271− },
2272− {
2273− "nameExplicit": true,
2274− "columns": [
2275− {
2276− "value": "organization_id",
2277− "isExpression": false,
2278− "asc": true,
2279− "nullsFirst": false,
2280− "opclass": null
2281− }
2282− ],
2283− "isUnique": false,
2284− "where": null,
2285− "with": "",
2286− "method": "btree",
2287− "concurrently": false,
2288− "name": "organization_memberships_org_idx",
2289− "entityType": "indexes",
2290− "schema": "public",
2291− "table": "organization_memberships"
2292− },
2293− {
2294− "nameExplicit": true,
2295− "columns": [
2296− {
2297− "value": "account_id",
2298− "isExpression": false,
2299− "asc": true,
2300− "nullsFirst": false,
2301− "opclass": null
2302− }
2303− ],
2304− "isUnique": false,
2305− "where": null,
2306− "with": "",
2307− "method": "btree",
2308− "concurrently": false,
2309− "name": "organization_memberships_account_idx",
2310− "entityType": "indexes",
2311− "schema": "public",
2312− "table": "organization_memberships"
2313− },
2314− {
2315− "nameExplicit": true,
2316− "columns": [
2317− {
2318− "value": "organization_id",
2319− "isExpression": false,
2320− "asc": true,
2321− "nullsFirst": false,
2322− "opclass": null
2323− },
2324− {
2325− "value": "account_id",
2326− "isExpression": false,
2327− "asc": true,
2328− "nullsFirst": false,
2329− "opclass": null
2330− }
2331− ],
2332− "isUnique": true,
2333− "where": null,
2334− "with": "",
2335− "method": "btree",
2336− "concurrently": false,
2337− "name": "organization_memberships_org_account_idx",
2338− "entityType": "indexes",
2339− "schema": "public",
2340− "table": "organization_memberships"
2341− },
2342− {
2343− "nameExplicit": true,
2344− "columns": [
2345− {
2346− "value": "slug",
2347− "isExpression": false,
2348− "asc": true,
2349− "nullsFirst": false,
2350− "opclass": null
2351− }
2352− ],
2353− "isUnique": true,
2354− "where": null,
2355− "with": "",
2356− "method": "btree",
2357− "concurrently": false,
2358− "name": "organizations_slug_idx",
2359− "entityType": "indexes",
2360− "schema": "public",
2361− "table": "organizations"
2362− },
2363− {
2364− "nameExplicit": true,
2365− "columns": [
2366− {
2367− "value": "updated_at",
2368− "isExpression": false,
2369− "asc": true,
2370− "nullsFirst": false,
2371− "opclass": null
2372− },
2373− {
2374− "value": "id",
2375− "isExpression": false,
2376− "asc": true,
2377− "nullsFirst": false,
2378− "opclass": null
2379− }
2380− ],
2381− "isUnique": false,
2382− "where": null,
2383− "with": "",
2384− "method": "btree",
2385− "concurrently": false,
2386− "name": "organizations_updated_idx",
2387− "entityType": "indexes",
2388− "schema": "public",
2389− "table": "organizations"
2390− },
2391− {
2392− "nameExplicit": true,
2393− "columns": [
2394− {
2395− "value": "workspace_id",
2396− "isExpression": false,
2397− "asc": true,
2398− "nullsFirst": false,
2399− "opclass": null
2400− },
2401− {
2402− "value": "updated_at",
2403− "isExpression": false,
2404− "asc": true,
2405− "nullsFirst": false,
2406− "opclass": null
2407− },
2408− {
2409− "value": "id",
2410− "isExpression": false,
2411− "asc": true,
2412− "nullsFirst": false,
2413− "opclass": null
2414− }
2415− ],
2416− "isUnique": false,
2417− "where": null,
2418− "with": "",
2419− "method": "btree",
2420− "concurrently": false,
2421− "name": "policies_workspace_idx",
2422− "entityType": "indexes",
2423− "schema": "public",
2424− "table": "policies"
2425− },
2426− {
2427− "nameExplicit": true,
2428− "columns": [
2429− {
2430− "value": "updated_at",
2431− "isExpression": false,
2432− "asc": true,
2433− "nullsFirst": false,
2434− "opclass": null
2435− },
2436− {
2437− "value": "id",
2438− "isExpression": false,
2439− "asc": true,
2440− "nullsFirst": false,
2441− "opclass": null
2442− }
2443− ],
2444− "isUnique": false,
2445− "where": null,
2446− "with": "",
2447− "method": "btree",
2448− "concurrently": false,
2449− "name": "secret_materials_updated_idx",
2450− "entityType": "indexes",
2451− "schema": "public",
2452− "table": "secret_materials"
2453− },
2454− {
2455− "nameExplicit": true,
2456− "columns": [
2457− {
2458− "value": "workspace_id",
2459− "isExpression": false,
2460− "asc": true,
2461− "nullsFirst": false,
2462− "opclass": null
2463− },
2464− {
2465− "value": "updated_at",
2466− "isExpression": false,
2467− "asc": true,
2468− "nullsFirst": false,
2469− "opclass": null
2470− },
2471− {
2472− "value": "id",
2473− "isExpression": false,
2474− "asc": true,
2475− "nullsFirst": false,
2476− "opclass": null
2477− }
2478− ],
2479− "isUnique": false,
2480− "where": null,
2481− "with": "",
2482− "method": "btree",
2483− "concurrently": false,
2484− "name": "source_auth_sessions_workspace_idx",
2485− "entityType": "indexes",
2486− "schema": "public",
2487− "table": "source_auth_sessions"
2488− },
2489− {
2490− "nameExplicit": true,
2491− "columns": [
2492− {
2493− "value": "state",
2494− "isExpression": false,
2495− "asc": true,
2496− "nullsFirst": false,
2497− "opclass": null
2498− }
2499− ],
2500− "isUnique": true,
2501− "where": null,
2502− "with": "",
2503− "method": "btree",
2504− "concurrently": false,
2505− "name": "source_auth_sessions_state_idx",
2506− "entityType": "indexes",
2507− "schema": "public",
2508− "table": "source_auth_sessions"
2509− },
2510− {
2511− "nameExplicit": true,
2512− "columns": [
2513− {
2514− "value": "workspace_id",
2515− "isExpression": false,
2516− "asc": true,
2517− "nullsFirst": false,
2518− "opclass": null
2519− },
2520− {
2521− "value": "updated_at",
2522− "isExpression": false,
2523− "asc": true,
2524− "nullsFirst": false,
2525− "opclass": null
2526− },
2527− {
2528− "value": "source_id",
2529− "isExpression": false,
2530− "asc": true,
2531− "nullsFirst": false,
2532− "opclass": null
2533− }
2534− ],
2535− "isUnique": false,
2536− "where": null,
2537− "with": "",
2538− "method": "btree",
2539− "concurrently": false,
2540− "name": "source_credential_bindings_workspace_idx",
2541− "entityType": "indexes",
2542− "schema": "public",
2543− "table": "source_credential_bindings"
2544− },
2545− {
2546− "nameExplicit": true,
2547− "columns": [
2548− {
2549− "value": "workspace_id",
2550− "isExpression": false,
2551− "asc": true,
2552− "nullsFirst": false,
2553− "opclass": null
2554− },
2555− {
2556− "value": "name",
2557− "isExpression": false,
2558− "asc": true,
2559− "nullsFirst": false,
2560− "opclass": null
2561− }
2562− ],
2563− "isUnique": true,
2564− "where": null,
2565− "with": "",
2566− "method": "btree",
2567− "concurrently": false,
2568− "name": "sources_workspace_name_idx",
2569− "entityType": "indexes",
2570− "schema": "public",
2571− "table": "sources"
2572− },
2573− {
2574− "nameExplicit": true,
2575− "columns": [
2576− {
2577− "value": "workspace_id",
2578− "isExpression": false,
2579− "asc": true,
2580− "nullsFirst": false,
2581− "opclass": null
2582− },
2583− {
2584− "value": "path",
2585− "isExpression": false,
2586− "asc": true,
2587− "nullsFirst": false,
2588− "opclass": null
2589− },
2590− {
2591− "value": "position",
2592− "isExpression": false,
2593− "asc": true,
2594− "nullsFirst": false,
2595− "opclass": null
2596− }
2597− ],
2598− "isUnique": false,
2599− "where": null,
2600− "with": "",
2601− "method": "btree",
2602− "concurrently": false,
2603− "name": "tool_artifact_parameters_lookup_idx",
2604− "entityType": "indexes",
2605− "schema": "public",
2606− "table": "tool_artifact_parameters"
2607− },
2608− {
2609− "nameExplicit": true,
2610− "columns": [
2611− {
2612− "value": "workspace_id",
2613− "isExpression": false,
2614− "asc": true,
2615− "nullsFirst": false,
2616− "opclass": null
2617− },
2618− {
2619− "value": "path",
2620− "isExpression": false,
2621− "asc": true,
2622− "nullsFirst": false,
2623− "opclass": null
2624− },
2625− {
2626− "value": "position",
2627− "isExpression": false,
2628− "asc": true,
2629− "nullsFirst": false,
2630− "opclass": null
2631− }
2632− ],
2633− "isUnique": false,
2634− "where": null,
2635− "with": "",
2636− "method": "btree",
2637− "concurrently": false,
2638− "name": "tool_artifact_ref_hint_keys_lookup_idx",
2639− "entityType": "indexes",
2640− "schema": "public",
2641− "table": "tool_artifact_ref_hint_keys"
2642− },
2643− {
2644− "nameExplicit": true,
2645− "columns": [
2646− {
2647− "value": "workspace_id",
2648− "isExpression": false,
2649− "asc": true,
2650− "nullsFirst": false,
2651− "opclass": null
2652− },
2653− {
2654− "value": "path",
2655− "isExpression": false,
2656− "asc": true,
2657− "nullsFirst": false,
2658− "opclass": null
2659− },
2660− {
2661− "value": "position",
2662− "isExpression": false,
2663− "asc": true,
2664− "nullsFirst": false,
2665− "opclass": null
2666− }
2667− ],
2668− "isUnique": false,
2669− "where": null,
2670− "with": "",
2671− "method": "btree",
2672− "concurrently": false,
2673− "name": "tool_artifact_request_body_content_types_lookup_idx",
2674− "entityType": "indexes",
2675− "schema": "public",
2676− "table": "tool_artifact_request_body_content_types"
2677− },
2678− {
2679− "nameExplicit": true,
2680− "columns": [
2681− {
2682− "value": "workspace_id",
2683− "isExpression": false,
2684− "asc": true,
2685− "nullsFirst": false,
2686− "opclass": null
2687− },
2688− {
2689− "value": "source_id",
2690− "isExpression": false,
2691− "asc": true,
2692− "nullsFirst": false,
2693− "opclass": null
2694− },
2695− {
2696− "value": "updated_at",
2697− "isExpression": false,
2698− "asc": true,
2699− "nullsFirst": false,
2700− "opclass": null
2701− },
2702− {
2703− "value": "path",
2704− "isExpression": false,
2705− "asc": true,
2706− "nullsFirst": false,
2707− "opclass": null
2708− }
2709− ],
2710− "isUnique": false,
2711− "where": null,
2712− "with": "",
2713− "method": "btree",
2714− "concurrently": false,
2715− "name": "tool_artifacts_workspace_source_idx",
2716− "entityType": "indexes",
2717− "schema": "public",
2718− "table": "tool_artifacts"
2719− },
2720− {
2721− "nameExplicit": true,
2722− "columns": [
2723− {
2724− "value": "workspace_id",
2725− "isExpression": false,
2726− "asc": true,
2727− "nullsFirst": false,
2728− "opclass": null
2729− },
2730− {
2731− "value": "search_namespace",
2732− "isExpression": false,
2733− "asc": true,
2734− "nullsFirst": false,
2735− "opclass": null
2736− },
2737− {
2738− "value": "path",
2739− "isExpression": false,
2740− "asc": true,
2741− "nullsFirst": false,
2742− "opclass": null
2743− }
2744− ],
2745− "isUnique": false,
2746− "where": null,
2747− "with": "",
2748− "method": "btree",
2749− "concurrently": false,
2750− "name": "tool_artifacts_workspace_namespace_idx",
2751− "entityType": "indexes",
2752− "schema": "public",
2753− "table": "tool_artifacts"
2754− },
2755− {
2756− "nameExplicit": true,
2757− "columns": [
2758− {
2759− "value": "organization_id",
2760− "isExpression": false,
2761− "asc": true,
2762− "nullsFirst": false,
2763− "opclass": null
2764− }
2765− ],
2766− "isUnique": false,
2767− "where": null,
2768− "with": "",
2769− "method": "btree",
2770− "concurrently": false,
2771− "name": "workspaces_org_idx",
2772− "entityType": "indexes",
2773− "schema": "public",
2774− "table": "workspaces"
2775− },
2776− {
2777− "nameExplicit": true,
2778− "columns": [
2779− {
2780− "value": "organization_id",
2781− "isExpression": false,
2782− "asc": true,
2783− "nullsFirst": false,
2784− "opclass": null
2785− },
2786− {
2787− "value": "name",
2788− "isExpression": false,
2789− "asc": true,
2790− "nullsFirst": false,
2791− "opclass": null
2792− }
2793− ],
2794− "isUnique": true,
2795− "where": null,
2796− "with": "",
2797− "method": "btree",
2798− "concurrently": false,
2799− "name": "workspaces_org_name_idx",
2800− "entityType": "indexes",
2801− "schema": "public",
2802− "table": "workspaces"
2803− },
2804− {
2805− "columns": [
2806− "workspace_id",
2807− "source_id"
2808− ],
2809− "nameExplicit": false,
2810− "name": "source_credential_bindings_pkey",
2811− "entityType": "pks",
2812− "schema": "public",
2813− "table": "source_credential_bindings"
2814− },
2815− {
2816− "columns": [
2817− "workspace_id",
2818− "source_id"
2819− ],
2820− "nameExplicit": false,
2821− "name": "sources_pkey",
2822− "entityType": "pks",
2823− "schema": "public",
2824− "table": "sources"
2825− },
2826− {
2827− "columns": [
2828− "workspace_id",
2829− "path",
2830− "position"
2831− ],
2832− "nameExplicit": false,
2833− "name": "tool_artifact_parameters_pkey",
2834− "entityType": "pks",
2835− "schema": "public",
2836− "table": "tool_artifact_parameters"
2837− },
2838− {
2839− "columns": [
2840− "workspace_id",
2841− "path",
2842− "position"
2843− ],
2844− "nameExplicit": false,
2845− "name": "tool_artifact_ref_hint_keys_pkey",
2846− "entityType": "pks",
2847− "schema": "public",
2848− "table": "tool_artifact_ref_hint_keys"
2849− },
2850− {
2851− "columns": [
2852− "workspace_id",
2853− "path",
2854− "position"
2855− ],
2856− "nameExplicit": false,
2857− "name": "tool_artifact_request_body_content_types_pkey",
2858− "entityType": "pks",
2859− "schema": "public",
2860− "table": "tool_artifact_request_body_content_types"
2861− },
2862− {
2863− "columns": [
2864− "workspace_id",
2865− "path"
2866− ],
2867− "nameExplicit": false,
2868− "name": "tool_artifacts_pkey",
2869− "entityType": "pks",
2870− "schema": "public",
2871− "table": "tool_artifacts"
2872− },
2873− {
2874− "columns": [
2875− "id"
2876− ],
2877− "nameExplicit": false,
2878− "name": "accounts_pkey",
2879− "schema": "public",
2880− "table": "accounts",
2881− "entityType": "pks"
2882− },
2883− {
2884− "columns": [
2885− "id"
2886− ],
2887− "nameExplicit": false,
2888− "name": "execution_interactions_pkey",
2889− "schema": "public",
2890− "table": "execution_interactions",
2891− "entityType": "pks"
2892− },
2893− {
2894− "columns": [
2895− "id"
2896− ],
2897− "nameExplicit": false,
2898− "name": "executions_pkey",
2899− "schema": "public",
2900− "table": "executions",
2901− "entityType": "pks"
2902− },
2903− {
2904− "columns": [
2905− "id"
2906− ],
2907− "nameExplicit": false,
2908− "name": "local_installations_pkey",
2909− "schema": "public",
2910− "table": "local_installations",
2911− "entityType": "pks"
2912− },
2913− {
2914− "columns": [
2915− "id"
2916− ],
2917− "nameExplicit": false,
2918− "name": "organization_memberships_pkey",
2919− "schema": "public",
2920− "table": "organization_memberships",
2921− "entityType": "pks"
2922− },
2923− {
2924− "columns": [
2925− "id"
2926− ],
2927− "nameExplicit": false,
2928− "name": "organizations_pkey",
2929− "schema": "public",
2930− "table": "organizations",
2931− "entityType": "pks"
2932− },
2933− {
2934− "columns": [
2935− "id"
2936− ],
2937− "nameExplicit": false,
2938− "name": "policies_pkey",
2939− "schema": "public",
2940− "table": "policies",
2941− "entityType": "pks"
2942− },
2943− {
2944− "columns": [
2945− "id"
2946− ],
2947− "nameExplicit": false,
2948− "name": "secret_materials_pkey",
2949− "schema": "public",
2950− "table": "secret_materials",
2951− "entityType": "pks"
2952− },
2953− {
2954− "columns": [
2955− "id"
2956− ],
2957− "nameExplicit": false,
2958− "name": "source_auth_sessions_pkey",
2959− "schema": "public",
2960− "table": "source_auth_sessions",
2961− "entityType": "pks"
2962− },
2963− {
2964− "columns": [
2965− "id"
2966− ],
2967− "nameExplicit": false,
2968− "name": "workspaces_pkey",
2969− "schema": "public",
2970− "table": "workspaces",
2971− "entityType": "pks"
2972− },
2973− {
2974− "value": "\"provider\" in ('local', 'workos', 'service')",
2975− "name": "accounts_provider_check",
2976− "entityType": "checks",
2977− "schema": "public",
2978− "table": "accounts"
2979− },
2980− {
2981− "value": "\"status\" in ('pending', 'resolved', 'cancelled')",
2982− "name": "execution_interactions_status_check",
2983− "entityType": "checks",
2984− "schema": "public",
2985− "table": "execution_interactions"
2986− },
2987− {
2988− "value": "\"status\" in ('pending', 'running', 'waiting_for_interaction', 'completed', 'failed', 'cancelled')",
2989− "name": "executions_status_check",
2990− "entityType": "checks",
2991− "schema": "public",
2992− "table": "executions"
2993− },
2994− {
2995− "value": "\"role\" in ('viewer', 'editor', 'admin', 'owner')",
2996− "name": "organization_memberships_role_check",
2997− "entityType": "checks",
2998− "schema": "public",
2999− "table": "organization_memberships"
3000− },
3001− {
3002− "value": "\"status\" in ('invited', 'active', 'suspended', 'removed')",
3003− "name": "organization_memberships_status_check",
3004− "entityType": "checks",
3005− "schema": "public",
3006− "table": "organization_memberships"
3007− },
3008− {
3009− "value": "\"status\" in ('active', 'suspended', 'archived')",
3010− "name": "organizations_status_check",
3011− "entityType": "checks",
3012− "schema": "public",
3013− "table": "organizations"
3014− },
3015− {
3016− "value": "\"resource_type\" in ('all_tools', 'source', 'namespace', 'tool_path')",
3017− "name": "policies_resource_type_check",
3018− "entityType": "checks",
3019− "schema": "public",
3020− "table": "policies"
3021− },
3022− {
3023− "value": "\"match_type\" in ('glob', 'exact')",
3024− "name": "policies_match_type_check",
3025− "entityType": "checks",
3026− "schema": "public",
3027− "table": "policies"
3028− },
3029− {
3030− "value": "\"effect\" in ('allow', 'deny')",
3031− "name": "policies_effect_check",
3032− "entityType": "checks",
3033− "schema": "public",
3034− "table": "policies"
3035− },
3036− {
3037− "value": "\"approval_mode\" in ('auto', 'required')",
3038− "name": "policies_approval_mode_check",
3039− "entityType": "checks",
3040− "schema": "public",
3041− "table": "policies"
3042− },
3043− {
3044− "value": "\"purpose\" in ('auth_material', 'oauth_access_token', 'oauth_refresh_token', 'oauth_client_info')",
3045− "name": "secret_materials_purpose_check",
3046− "entityType": "checks",
3047− "schema": "public",
3048− "table": "secret_materials"
3049− },
3050− {
3051− "value": "\"strategy\" in ('oauth2_authorization_code')",
3052− "name": "source_auth_sessions_strategy_check",
3053− "entityType": "checks",
3054− "schema": "public",
3055− "table": "source_auth_sessions"
3056− },
3057− {
3058− "value": "\"status\" in ('pending', 'completed', 'failed', 'cancelled')",
3059− "name": "source_auth_sessions_status_check",
3060− "entityType": "checks",
3061− "schema": "public",
3062− "table": "source_auth_sessions"
3063− },
3064− {
3065− "value": "\"kind\" in ('mcp', 'openapi', 'graphql', 'internal')",
3066− "name": "sources_kind_check",
3067− "entityType": "checks",
3068− "schema": "public",
3069− "table": "sources"
3070− },
3071− {
3072− "value": "\"status\" in ('draft', 'probing', 'auth_required', 'connected', 'error')",
3073− "name": "sources_status_check",
3074− "entityType": "checks",
3075− "schema": "public",
3076− "table": "sources"
3077− },
3078− {
3079− "value": "\"transport\" is null or \"transport\" in ('auto', 'streamable-http', 'sse')",
3080− "name": "sources_transport_check",
3081− "entityType": "checks",
3082− "schema": "public",
3083− "table": "sources"
3084− },
3085− {
3086− "value": "\"auth_kind\" in ('none', 'bearer', 'oauth2')",
3087− "name": "sources_auth_kind_check",
3088− "entityType": "checks",
3089− "schema": "public",
3090− "table": "sources"
3091− },
3092− {
3093− "value": "\"location\" in ('path', 'query', 'header', 'cookie')",
3094− "name": "tool_artifact_parameters_location_check",
3095− "entityType": "checks",
3096− "schema": "public",
3097− "table": "tool_artifact_parameters"
3098− },
3099− {
3100− "value": "\"provider_kind\" in ('mcp', 'openapi')",
3101− "name": "tool_artifacts_provider_kind_check",
3102− "entityType": "checks",
3103− "schema": "public",
3104− "table": "tool_artifacts"
3105− },
3106− {
3107− "value": "\"provider_kind\" <> 'mcp'\n or (\n \"mcp_tool_name\" is not null\n and \"openapi_method\" is null\n and \"openapi_path_template\" is null\n and \"openapi_operation_hash\" is null\n and \"openapi_request_body_required\" is null\n )",
3108− "name": "tool_artifacts_mcp_shape_check",
3109− "entityType": "checks",
3110− "schema": "public",
3111− "table": "tool_artifacts"
3112− },
3113− {
3114− "value": "\"provider_kind\" <> 'openapi'\n or (\n \"mcp_tool_name\" is null\n and \"openapi_method\" in ('get', 'put', 'post', 'delete', 'patch', 'head', 'options', 'trace')\n and \"openapi_path_template\" is not null\n and \"openapi_operation_hash\" is not null\n )",
3115− "name": "tool_artifacts_openapi_shape_check",
3116− "entityType": "checks",
3117− "schema": "public",
3118− "table": "tool_artifacts"
3119− }
3120− ],
3121− "renames": []
3122−}
packages/control-plane/drizzle/20260307134000_openapi_tool_identity/migration.sql+0−37
@@ -1,37 +0,0 @@
1−ALTER TABLE "tool_artifacts" ADD COLUMN "openapi_raw_tool_id" text;--> statement-breakpoint
2−ALTER TABLE "tool_artifacts" ADD COLUMN "openapi_operation_id" text;--> statement-breakpoint
3−ALTER TABLE "tool_artifacts" ADD COLUMN "openapi_tags_json" text;--> statement-breakpoint
4−UPDATE "tool_artifacts"
5−SET
6− "openapi_raw_tool_id" = "tool_id",
7− "openapi_operation_id" = "tool_id",
8− "openapi_tags_json" = '[]'
9−WHERE "provider_kind" = 'openapi';--> statement-breakpoint
10−ALTER TABLE "tool_artifacts" DROP CONSTRAINT "tool_artifacts_mcp_shape_check";--> statement-breakpoint
11−ALTER TABLE "tool_artifacts" DROP CONSTRAINT "tool_artifacts_openapi_shape_check";--> statement-breakpoint
12−ALTER TABLE "tool_artifacts"
13−ADD CONSTRAINT "tool_artifacts_mcp_shape_check" CHECK (
14− "provider_kind" <> 'mcp'
15− OR (
16− "mcp_tool_name" is not null
17− and "openapi_method" is null
18− and "openapi_path_template" is null
19− and "openapi_operation_hash" is null
20− and "openapi_raw_tool_id" is null
21− and "openapi_operation_id" is null
22− and "openapi_tags_json" is null
23− and "openapi_request_body_required" is null
24− )
25−);--> statement-breakpoint
26−ALTER TABLE "tool_artifacts"
27−ADD CONSTRAINT "tool_artifacts_openapi_shape_check" CHECK (
28− "provider_kind" <> 'openapi'
29− OR (
30− "mcp_tool_name" is null
31− and "openapi_method" in ('get', 'put', 'post', 'delete', 'patch', 'head', 'options', 'trace')
32− and "openapi_path_template" is not null
33− and "openapi_operation_hash" is not null
34− and "openapi_raw_tool_id" is not null
35− and "openapi_tags_json" is not null
36− )
37−);
packages/control-plane/drizzle/20260307152000_source_document_text/migration.sql+0−1
@@ -1 +0,0 @@
1−ALTER TABLE "sources" ADD COLUMN "source_document_text" text;
packages/control-plane/drizzle/20260308041000_credentials_model/migration.sql+0−41
@@ -1,41 +0,0 @@
1−CREATE TABLE "credentials" (
2− "id" text PRIMARY KEY NOT NULL,
3− "workspace_id" text NOT NULL,
4− "auth_kind" text NOT NULL,
5− "auth_header_name" text NOT NULL,
6− "auth_prefix" text NOT NULL,
7− "token_provider_id" text NOT NULL,
8− "token_handle" text NOT NULL,
9− "refresh_token_provider_id" text,
10− "refresh_token_handle" text,
11− "created_at" bigint NOT NULL,
12− "updated_at" bigint NOT NULL,
13− CONSTRAINT "credentials_auth_kind_check" CHECK ("auth_kind" in ('bearer', 'oauth2'))
14−);
15−--> statement-breakpoint
16−CREATE INDEX "credentials_workspace_idx" ON "credentials" ("workspace_id","updated_at","id");
17−--> statement-breakpoint
18−ALTER TABLE "sources" DROP CONSTRAINT "sources_auth_kind_check";
19−--> statement-breakpoint
20−ALTER TABLE "sources" DROP COLUMN "auth_kind";
21−--> statement-breakpoint
22−ALTER TABLE "sources" DROP COLUMN "auth_header_name";
23−--> statement-breakpoint
24−ALTER TABLE "sources" DROP COLUMN "auth_prefix";
25−--> statement-breakpoint
26−DROP INDEX IF EXISTS "source_credential_bindings_workspace_idx";
27−--> statement-breakpoint
28−DROP TABLE "source_credential_bindings";
29−--> statement-breakpoint
30−CREATE TABLE "source_credential_bindings" (
31− "id" text PRIMARY KEY NOT NULL,
32− "workspace_id" text NOT NULL,
33− "source_id" text NOT NULL,
34− "credential_id" text NOT NULL,
35− "created_at" bigint NOT NULL,
36− "updated_at" bigint NOT NULL
37−);
38−--> statement-breakpoint
39−CREATE UNIQUE INDEX "source_credential_bindings_workspace_source_idx" ON "source_credential_bindings" ("workspace_id","source_id");
40−--> statement-breakpoint
41−CREATE INDEX "source_credential_bindings_workspace_idx" ON "source_credential_bindings" ("workspace_id","updated_at","source_id");
packages/control-plane/drizzle/20260308120000_search_and_indexes/migration.sql+0−16
@@ -1,16 +0,0 @@
1−DROP INDEX IF EXISTS "organization_memberships_org_idx";--> statement-breakpoint
2−DROP INDEX IF EXISTS "organization_memberships_account_idx";--> statement-breakpoint
3−CREATE INDEX "organization_memberships_org_updated_idx" ON "organization_memberships" ("organization_id","updated_at","id");--> statement-breakpoint
4−CREATE INDEX "organization_memberships_account_updated_idx" ON "organization_memberships" ("account_id","updated_at","id");--> statement-breakpoint
5−DROP INDEX IF EXISTS "workspaces_org_idx";--> statement-breakpoint
6−CREATE INDEX "workspaces_org_updated_idx" ON "workspaces" ("organization_id","updated_at","id");--> statement-breakpoint
7−CREATE INDEX "sources_workspace_updated_idx" ON "sources" ("workspace_id","updated_at","source_id");--> statement-breakpoint
8−CREATE INDEX "tool_artifacts_search_text_idx" ON "tool_artifacts" USING gin (to_tsvector('simple', "search_text"));--> statement-breakpoint
9−DROP INDEX IF EXISTS "tool_artifact_parameters_lookup_idx";--> statement-breakpoint
10−DROP INDEX IF EXISTS "tool_artifact_request_body_content_types_lookup_idx";--> statement-breakpoint
11−DROP INDEX IF EXISTS "tool_artifact_ref_hint_keys_lookup_idx";--> statement-breakpoint
12−CREATE INDEX "source_auth_sessions_pending_idx" ON "source_auth_sessions" ("workspace_id","source_id","status","updated_at","id");--> statement-breakpoint
13−DROP INDEX IF EXISTS "policies_workspace_idx";--> statement-breakpoint
14−CREATE INDEX "policies_workspace_priority_idx" ON "policies" ("workspace_id","priority" DESC,"updated_at","id");--> statement-breakpoint
15−CREATE INDEX "local_installations_organization_idx" ON "local_installations" ("organization_id");--> statement-breakpoint
16−CREATE INDEX "local_installations_workspace_idx" ON "local_installations" ("workspace_id");
packages/control-plane/drizzle/20260308140000_secret_materials_name/migration.sql+0−1
@@ -1 +0,0 @@
1−ALTER TABLE "secret_materials" ADD COLUMN "name" text;
packages/control-plane/drizzle/20260309103000_policy_scope_and_interaction_purpose/migration.sql+0−17
@@ -1,17 +0,0 @@
1−ALTER TABLE "policies" ADD COLUMN "scope_type" text;--> statement-breakpoint
2−ALTER TABLE "policies" ADD COLUMN "organization_id" text;--> statement-breakpoint
3−UPDATE "policies" AS "pol"
4−SET
5− "scope_type" = 'workspace',
6− "organization_id" = "ws"."organization_id"
7−FROM "workspaces" AS "ws"
8−WHERE "pol"."workspace_id" = "ws"."id";--> statement-breakpoint
9−ALTER TABLE "policies" ALTER COLUMN "scope_type" SET NOT NULL;--> statement-breakpoint
10−ALTER TABLE "policies" ALTER COLUMN "organization_id" SET NOT NULL;--> statement-breakpoint
11−ALTER TABLE "policies" ALTER COLUMN "workspace_id" DROP NOT NULL;--> statement-breakpoint
12−ALTER TABLE "policies" ADD CONSTRAINT "policies_scope_type_check" CHECK ("scope_type" in ('organization', 'workspace'));--> statement-breakpoint
13−ALTER TABLE "policies" ADD CONSTRAINT "policies_scope_consistency_check" CHECK (("scope_type" = 'organization' and "workspace_id" is null) or ("scope_type" = 'workspace' and "workspace_id" is not null));--> statement-breakpoint
14−DROP INDEX IF EXISTS "policies_workspace_priority_idx";--> statement-breakpoint
15−CREATE INDEX "policies_organization_priority_idx" ON "policies" ("organization_id","priority" DESC,"updated_at","id");--> statement-breakpoint
16−CREATE INDEX "policies_workspace_priority_idx" ON "policies" ("workspace_id","priority" DESC,"updated_at","id");--> statement-breakpoint
17−ALTER TABLE "execution_interactions" ADD COLUMN "purpose" text NOT NULL DEFAULT 'elicitation';
packages/control-plane/drizzle/20260310120000_source_recipes_and_actor_credentials/migration.sql+0−491
@@ -1,491 +0,0 @@
1−ALTER TABLE "sources" ADD COLUMN "recipe_id" text;--> statement-breakpoint
2−ALTER TABLE "sources" ADD COLUMN "recipe_revision_id" text;--> statement-breakpoint
3−ALTER TABLE "sources" ADD COLUMN "binding_config_json" text;--> statement-breakpoint
4−
5−CREATE TABLE "source_recipes" (
6− "id" text PRIMARY KEY NOT NULL,
7− "kind" text NOT NULL,
8− "importer_kind" text NOT NULL,
9− "provider_key" text NOT NULL,
10− "name" text NOT NULL,
11− "summary" text,
12− "visibility" text NOT NULL,
13− "latest_revision_id" text NOT NULL,
14− "created_at" bigint NOT NULL,
15− "updated_at" bigint NOT NULL,
16− CONSTRAINT "source_recipes_kind_check" CHECK ("kind" in ('http_recipe', 'graphql_recipe', 'mcp_recipe', 'internal_recipe')),
17− CONSTRAINT "source_recipes_importer_kind_check" CHECK ("importer_kind" in (
18− 'openapi',
19− 'google_discovery',
20− 'postman_collection',
21− 'snippet_bundle',
22− 'graphql_introspection',
23− 'mcp_manifest',
24− 'internal_manifest'
25− )),
26− CONSTRAINT "source_recipes_visibility_check" CHECK ("visibility" in ('private', 'workspace', 'organization', 'public'))
27−);--> statement-breakpoint
28−
29−CREATE TABLE "source_recipe_revisions" (
30− "id" text PRIMARY KEY NOT NULL,
31− "recipe_id" text NOT NULL,
32− "revision_number" bigint NOT NULL,
33− "source_config_json" text NOT NULL,
34− "manifest_json" text,
35− "manifest_hash" text,
36− "created_at" bigint NOT NULL,
37− "updated_at" bigint NOT NULL
38−);--> statement-breakpoint
39−
40−CREATE TABLE "source_recipe_documents" (
41− "id" text PRIMARY KEY NOT NULL,
42− "recipe_revision_id" text NOT NULL,
43− "document_kind" text NOT NULL,
44− "document_key" text NOT NULL,
45− "content_text" text NOT NULL,
46− "content_hash" text NOT NULL,
47− "fetched_at" bigint,
48− "created_at" bigint NOT NULL,
49− "updated_at" bigint NOT NULL,
50− CONSTRAINT "source_recipe_documents_kind_check" CHECK ("document_kind" in (
51− 'google_discovery',
52− 'openapi',
53− 'postman_collection',
54− 'postman_environment',
55− 'graphql_introspection',
56− 'mcp_manifest'
57− ))
58−);--> statement-breakpoint
59−
60−CREATE TABLE "source_recipe_operations" (
61− "id" text PRIMARY KEY NOT NULL,
62− "recipe_revision_id" text NOT NULL,
63− "operation_key" text NOT NULL,
64− "transport_kind" text NOT NULL,
65− "tool_id" text NOT NULL,
66− "title" text,
67− "description" text,
68− "operation_kind" text NOT NULL,
69− "search_text" text NOT NULL,
70− "input_schema_json" text,
71− "output_schema_json" text,
72− "provider_kind" text NOT NULL,
73− "provider_data_json" text,
74− "mcp_tool_name" text,
75− "openapi_method" text,
76− "openapi_path_template" text,
77− "openapi_operation_hash" text,
78− "openapi_raw_tool_id" text,
79− "openapi_operation_id" text,
80− "openapi_tags_json" text,
81− "openapi_request_body_required" boolean,
82− "graphql_operation_type" text,
83− "graphql_operation_name" text,
84− "created_at" bigint NOT NULL,
85− "updated_at" bigint NOT NULL,
86− CONSTRAINT "source_recipe_operations_transport_kind_check" CHECK ("transport_kind" in ('http', 'graphql', 'mcp', 'internal')),
87− CONSTRAINT "source_recipe_operations_kind_check" CHECK ("operation_kind" in ('read', 'write', 'delete', 'unknown')),
88− CONSTRAINT "source_recipe_operations_provider_kind_check" CHECK ("provider_kind" in ('mcp', 'openapi', 'graphql', 'internal'))
89−);--> statement-breakpoint
90−
91−CREATE TABLE "workspace_source_oauth_clients" (
92− "id" text PRIMARY KEY NOT NULL,
93− "workspace_id" text NOT NULL,
94− "source_id" text NOT NULL,
95− "provider_key" text NOT NULL,
96− "client_id" text NOT NULL,
97− "client_secret_provider_id" text,
98− "client_secret_handle" text,
99− "client_metadata_json" text,
100− "created_at" bigint NOT NULL,
101− "updated_at" bigint NOT NULL
102−);--> statement-breakpoint
103−
104−UPDATE "sources"
105−SET
106− "recipe_id" = 'src_recipe_' || md5("workspace_id" || ':' || "source_id"),
107− "recipe_revision_id" = 'src_recipe_rev_' || md5("workspace_id" || ':' || "source_id"),
108− "binding_config_json" = null;--> statement-breakpoint
109−
110−INSERT INTO "source_recipes" (
111− "id",
112− "kind",
113− "importer_kind",
114− "provider_key",
115− "name",
116− "summary",
117− "visibility",
118− "latest_revision_id",
119− "created_at",
120− "updated_at"
121−)
122−SELECT
123− "s"."recipe_id",
124− CASE
125− WHEN "s"."kind" = 'openapi' THEN 'http_recipe'
126− WHEN "s"."kind" = 'graphql' THEN 'graphql_recipe'
127− WHEN "s"."kind" = 'mcp' THEN 'mcp_recipe'
128− ELSE 'internal_recipe'
129− END,
130− CASE
131− WHEN "s"."kind" = 'openapi' THEN 'openapi'
132− WHEN "s"."kind" = 'graphql' THEN 'graphql_introspection'
133− WHEN "s"."kind" = 'mcp' THEN 'mcp_manifest'
134− ELSE 'internal_manifest'
135− END,
136− CASE
137− WHEN "s"."kind" = 'openapi' THEN 'generic_http'
138− WHEN "s"."kind" = 'graphql' THEN 'generic_graphql'
139− WHEN "s"."kind" = 'mcp' THEN 'generic_mcp'
140− ELSE 'generic_internal'
141− END,
142− "s"."name",
143− null,
144− 'workspace',
145− "s"."recipe_revision_id",
146− "s"."created_at",
147− "s"."updated_at"
148−FROM "sources" AS "s";--> statement-breakpoint
149−
150−INSERT INTO "source_recipe_revisions" (
151− "id",
152− "recipe_id",
153− "revision_number",
154− "source_config_json",
155− "manifest_json",
156− "manifest_hash",
157− "created_at",
158− "updated_at"
159−)
160−SELECT
161− "s"."recipe_revision_id",
162− "s"."recipe_id",
163− 1,
164− CASE
165− WHEN "s"."kind" = 'mcp' THEN jsonb_build_object(
166− 'kind', 'mcp',
167− 'endpoint', "s"."endpoint",
168− 'transport', "s"."transport",
169− 'queryParams', COALESCE("s"."query_params_json"::jsonb, 'null'::jsonb),
170− 'headers', COALESCE("s"."headers_json"::jsonb, 'null'::jsonb)
171− )::text
172− WHEN "s"."kind" = 'openapi' THEN jsonb_build_object(
173− 'kind', 'openapi',
174− 'endpoint', "s"."endpoint",
175− 'specUrl', COALESCE("s"."spec_url", "s"."endpoint"),
176− 'defaultHeaders', COALESCE("s"."default_headers_json"::jsonb, 'null'::jsonb)
177− )::text
178− WHEN "s"."kind" = 'graphql' THEN jsonb_build_object(
179− 'kind', 'graphql',
180− 'endpoint', "s"."endpoint",
181− 'defaultHeaders', COALESCE("s"."default_headers_json"::jsonb, 'null'::jsonb)
182− )::text
183− ELSE jsonb_build_object(
184− 'kind', 'internal',
185− 'endpoint', "s"."endpoint"
186− )::text
187− END,
188− null,
189− "s"."source_hash",
190− "s"."created_at",
191− "s"."updated_at"
192−FROM "sources" AS "s";--> statement-breakpoint
193−
194−INSERT INTO "source_recipe_documents" (
195− "id",
196− "recipe_revision_id",
197− "document_kind",
198− "document_key",
199− "content_text",
200− "content_hash",
201− "fetched_at",
202− "created_at",
203− "updated_at"
204−)
205−SELECT
206− 'src_recipe_doc_' || md5("s"."workspace_id" || ':' || "s"."source_id" || ':primary'),
207− "s"."recipe_revision_id",
208− CASE
209− WHEN "s"."kind" = 'openapi' THEN 'openapi'
210− WHEN "s"."kind" = 'graphql' THEN 'graphql_introspection'
211− ELSE 'mcp_manifest'
212− END,
213− CASE
214− WHEN "s"."kind" = 'openapi' THEN COALESCE("s"."spec_url", "s"."endpoint")
215− ELSE "s"."endpoint"
216− END,
217− "s"."source_document_text",
218− md5("s"."source_document_text"),
219− "s"."updated_at",
220− "s"."created_at",
221− "s"."updated_at"
222−FROM "sources" AS "s"
223−WHERE "s"."source_document_text" IS NOT NULL;--> statement-breakpoint
224−
225−INSERT INTO "source_recipe_operations" (
226− "id",
227− "recipe_revision_id",
228− "operation_key",
229− "transport_kind",
230− "tool_id",
231− "title",
232− "description",
233− "operation_kind",
234− "search_text",
235− "input_schema_json",
236− "output_schema_json",
237− "provider_kind",
238− "provider_data_json",
239− "mcp_tool_name",
240− "openapi_method",
241− "openapi_path_template",
242− "openapi_operation_hash",
243− "openapi_raw_tool_id",
244− "openapi_operation_id",
245− "openapi_tags_json",
246− "openapi_request_body_required",
247− "graphql_operation_type",
248− "graphql_operation_name",
249− "created_at",
250− "updated_at"
251−)
252−SELECT
253− 'src_recipe_op_' || md5("ta"."workspace_id" || ':' || "ta"."source_id" || ':' || "ta"."path"),
254− "s"."recipe_revision_id",
255− "ta"."tool_id",
256− CASE
257− WHEN "ta"."provider_kind" = 'openapi' THEN 'http'
258− ELSE 'mcp'
259− END,
260− "ta"."tool_id",
261− "ta"."title",
262− "ta"."description",
263− CASE
264− WHEN "ta"."provider_kind" = 'openapi' AND upper(COALESCE("ta"."openapi_method", '')) IN ('GET', 'HEAD') THEN 'read'
265− WHEN "ta"."provider_kind" = 'openapi' AND upper(COALESCE("ta"."openapi_method", '')) = 'DELETE' THEN 'delete'
266− WHEN "ta"."provider_kind" = 'openapi' THEN 'write'
267− ELSE 'unknown'
268− END,
269− "ta"."search_text",
270− "ta"."input_schema_json",
271− "ta"."output_schema_json",
272− "ta"."provider_kind",
273− CASE
274− WHEN "ta"."provider_kind" = 'mcp' THEN jsonb_build_object(
275− 'kind', 'mcp',
276− 'toolId', "ta"."tool_id",
277− 'toolName', COALESCE("ta"."mcp_tool_name", "ta"."title", "ta"."path"),
278− 'description', "ta"."description"
279− )::text
280− ELSE jsonb_build_object(
281− 'kind', 'openapi',
282− 'toolId', "ta"."tool_id",
283− 'rawToolId', "ta"."openapi_raw_tool_id",
284− 'operationId', "ta"."openapi_operation_id",
285− 'tags', COALESCE("ta"."openapi_tags_json"::jsonb, '[]'::jsonb),
286− 'method', "ta"."openapi_method",
287− 'path', "ta"."openapi_path_template",
288− 'operationHash', "ta"."openapi_operation_hash"
289− )::text
290− END,
291− "ta"."mcp_tool_name",
292− "ta"."openapi_method",
293− "ta"."openapi_path_template",
294− "ta"."openapi_operation_hash",
295− "ta"."openapi_raw_tool_id",
296− "ta"."openapi_operation_id",
297− "ta"."openapi_tags_json",
298− "ta"."openapi_request_body_required",
299− null,
300− null,
301− "ta"."created_at",
302− "ta"."updated_at"
303−FROM "tool_artifacts" AS "ta"
304−JOIN "sources" AS "s"
305− ON "s"."workspace_id" = "ta"."workspace_id"
306− AND "s"."source_id" = "ta"."source_id";--> statement-breakpoint
307−
308−WITH "mcp_manifests" AS (
309− SELECT
310− "s"."workspace_id",
311− "s"."source_id",
312− "s"."recipe_revision_id",
313− jsonb_build_object(
314− 'version', 1,
315− 'tools', COALESCE(
316− jsonb_agg(
317− jsonb_build_object(
318− 'toolId', "ta"."tool_id",
319− 'toolName', COALESCE("ta"."mcp_tool_name", "ta"."title", "ta"."path"),
320− 'description', "ta"."description",
321− 'inputSchemaJson', "ta"."input_schema_json",
322− 'outputSchemaJson', "ta"."output_schema_json"
323− )
324− ORDER BY "ta"."path"
325− ) FILTER (WHERE "ta"."provider_kind" = 'mcp'),
326− '[]'::jsonb
327− )
328− )::text AS "manifest_json"
329− FROM "sources" AS "s"
330− LEFT JOIN "tool_artifacts" AS "ta"
331− ON "ta"."workspace_id" = "s"."workspace_id"
332− AND "ta"."source_id" = "s"."source_id"
333− WHERE "s"."kind" = 'mcp'
334− GROUP BY "s"."workspace_id", "s"."source_id", "s"."recipe_revision_id"
335−)
336−UPDATE "source_recipe_revisions" AS "rev"
337−SET
338− "manifest_json" = "mcp"."manifest_json",
339− "manifest_hash" = md5("mcp"."manifest_json")
340−FROM "mcp_manifests" AS "mcp"
341−WHERE "rev"."id" = "mcp"."recipe_revision_id";--> statement-breakpoint
342−
343−INSERT INTO "source_recipe_documents" (
344− "id",
345− "recipe_revision_id",
346− "document_kind",
347− "document_key",
348− "content_text",
349− "content_hash",
350− "fetched_at",
351− "created_at",
352− "updated_at"
353−)
354−SELECT
355− 'src_recipe_doc_' || md5("s"."workspace_id" || ':' || "s"."source_id" || ':mcp_manifest'),
356− "s"."recipe_revision_id",
357− 'mcp_manifest',
358− "s"."endpoint",
359− "rev"."manifest_json",
360− md5("rev"."manifest_json"),
361− "s"."updated_at",
362− "s"."created_at",
363− "s"."updated_at"
364−FROM "sources" AS "s"
365−JOIN "source_recipe_revisions" AS "rev"
366− ON "rev"."id" = "s"."recipe_revision_id"
367−WHERE "s"."kind" = 'mcp'
368− AND "rev"."manifest_json" IS NOT NULL
369− AND NOT EXISTS (
370− SELECT 1
371− FROM "source_recipe_documents" AS "doc"
372− WHERE "doc"."recipe_revision_id" = "s"."recipe_revision_id"
373− AND "doc"."document_kind" = 'mcp_manifest'
374− AND "doc"."document_key" = "s"."endpoint"
375− );--> statement-breakpoint
376−
377−ALTER TABLE "sources" ALTER COLUMN "recipe_id" SET NOT NULL;--> statement-breakpoint
378−ALTER TABLE "sources" ALTER COLUMN "recipe_revision_id" SET NOT NULL;--> statement-breakpoint
379−CREATE INDEX "sources_workspace_recipe_idx" ON "sources" ("workspace_id","recipe_id","updated_at","source_id");--> statement-breakpoint
380−
381−CREATE TABLE "workspace_source_credentials" (
382− "id" text PRIMARY KEY NOT NULL,
383− "workspace_id" text NOT NULL,
384− "source_id" text NOT NULL,
385− "actor_account_id" text,
386− "auth_kind" text NOT NULL,
387− "auth_header_name" text NOT NULL,
388− "auth_prefix" text NOT NULL,
389− "token_provider_id" text NOT NULL,
390− "token_handle" text NOT NULL,
391− "refresh_token_provider_id" text,
392− "refresh_token_handle" text,
393− "created_at" bigint NOT NULL,
394− "updated_at" bigint NOT NULL,
395− CONSTRAINT "credentials_auth_kind_check" CHECK ("auth_kind" in ('bearer', 'oauth2'))
396−);--> statement-breakpoint
397−
398−INSERT INTO "workspace_source_credentials" (
399− "id",
400− "workspace_id",
401− "source_id",
402− "actor_account_id",
403− "auth_kind",
404− "auth_header_name",
405− "auth_prefix",
406− "token_provider_id",
407− "token_handle",
408− "refresh_token_provider_id",
409− "refresh_token_handle",
410− "created_at",
411− "updated_at"
412−)
413−SELECT
414− CASE
415− WHEN count(*) OVER (PARTITION BY "c"."id") = 1 THEN "c"."id"
416− ELSE "c"."id" || ':' || "scb"."source_id"
417− END,
418− "c"."workspace_id",
419− "scb"."source_id",
420− null,
421− "c"."auth_kind",
422− "c"."auth_header_name",
423− "c"."auth_prefix",
424− "c"."token_provider_id",
425− "c"."token_handle",
426− "c"."refresh_token_provider_id",
427− "c"."refresh_token_handle",
428− "c"."created_at",
429− "c"."updated_at"
430−FROM "credentials" AS "c"
431−JOIN "source_credential_bindings" AS "scb"
432− ON "scb"."workspace_id" = "c"."workspace_id"
433− AND "scb"."credential_id" = "c"."id";--> statement-breakpoint
434−
435−DROP TABLE "credentials";--> statement-breakpoint
436−DROP TABLE "source_credential_bindings";--> statement-breakpoint
437−CREATE INDEX "credentials_workspace_idx" ON "workspace_source_credentials" ("workspace_id","updated_at","id");--> statement-breakpoint
438−CREATE UNIQUE INDEX "credentials_workspace_source_actor_idx" ON "workspace_source_credentials" ("workspace_id","source_id","actor_account_id");--> statement-breakpoint
439−CREATE INDEX "credentials_workspace_source_idx" ON "workspace_source_credentials" ("workspace_id","source_id","updated_at","id");--> statement-breakpoint
440−
441−ALTER TABLE "source_auth_sessions" ADD COLUMN "actor_account_id" text;--> statement-breakpoint
442−ALTER TABLE "source_auth_sessions" ADD COLUMN "provider_kind" text;--> statement-breakpoint
443−ALTER TABLE "source_auth_sessions" ADD COLUMN "session_data_json" text;--> statement-breakpoint
444−
445−UPDATE "source_auth_sessions"
446−SET
447− "provider_kind" = 'mcp_oauth',
448− "session_data_json" = jsonb_build_object(
449− 'kind', 'mcp_oauth',
450− 'endpoint', "endpoint",
451− 'redirectUri', "redirect_uri",
452− 'scope', "scope",
453− 'resourceMetadataUrl', "resource_metadata_url",
454− 'authorizationServerUrl', "authorization_server_url",
455− 'resourceMetadataJson', "resource_metadata_json",
456− 'authorizationServerMetadataJson', "authorization_server_metadata_json",
457− 'clientInformationJson', "client_information_json",
458− 'codeVerifier', "code_verifier",
459− 'authorizationUrl', "authorization_url"
460− )::text;--> statement-breakpoint
461−
462−ALTER TABLE "source_auth_sessions" ALTER COLUMN "provider_kind" SET NOT NULL;--> statement-breakpoint
463−ALTER TABLE "source_auth_sessions" ALTER COLUMN "session_data_json" SET NOT NULL;--> statement-breakpoint
464−DROP INDEX IF EXISTS "source_auth_sessions_pending_idx";--> statement-breakpoint
465−ALTER TABLE "source_auth_sessions" DROP CONSTRAINT "source_auth_sessions_strategy_check";--> statement-breakpoint
466−ALTER TABLE "source_auth_sessions" DROP COLUMN "strategy";--> statement-breakpoint
467−ALTER TABLE "source_auth_sessions" DROP COLUMN "endpoint";--> statement-breakpoint
468−ALTER TABLE "source_auth_sessions" DROP COLUMN "redirect_uri";--> statement-breakpoint
469−ALTER TABLE "source_auth_sessions" DROP COLUMN "scope";--> statement-breakpoint
470−ALTER TABLE "source_auth_sessions" DROP COLUMN "resource_metadata_url";--> statement-breakpoint
471−ALTER TABLE "source_auth_sessions" DROP COLUMN "authorization_server_url";--> statement-breakpoint
472−ALTER TABLE "source_auth_sessions" DROP COLUMN "resource_metadata_json";--> statement-breakpoint
473−ALTER TABLE "source_auth_sessions" DROP COLUMN "authorization_server_metadata_json";--> statement-breakpoint
474−ALTER TABLE "source_auth_sessions" DROP COLUMN "client_information_json";--> statement-breakpoint
475−ALTER TABLE "source_auth_sessions" DROP COLUMN "code_verifier";--> statement-breakpoint
476−ALTER TABLE "source_auth_sessions" DROP COLUMN "authorization_url";--> statement-breakpoint
477−ALTER TABLE "source_auth_sessions" ADD CONSTRAINT "source_auth_sessions_provider_kind_check" CHECK ("provider_kind" in ('mcp_oauth', 'oauth2_pkce'));--> statement-breakpoint
478−CREATE INDEX "source_auth_sessions_pending_idx" ON "source_auth_sessions" ("workspace_id","source_id","actor_account_id","status","updated_at","id");--> statement-breakpoint
479−
480−CREATE INDEX "source_recipes_provider_updated_idx" ON "source_recipes" ("provider_key","updated_at","id");--> statement-breakpoint
481−CREATE INDEX "source_recipes_visibility_updated_idx" ON "source_recipes" ("visibility","updated_at","id");--> statement-breakpoint
482−CREATE UNIQUE INDEX "source_recipe_revisions_recipe_revision_idx" ON "source_recipe_revisions" ("recipe_id","revision_number");--> statement-breakpoint
483−CREATE UNIQUE INDEX "source_recipe_revisions_recipe_manifest_idx" ON "source_recipe_revisions" ("recipe_id","manifest_hash");--> statement-breakpoint
484−CREATE INDEX "source_recipe_revisions_recipe_created_idx" ON "source_recipe_revisions" ("recipe_id","created_at","id");--> statement-breakpoint
485−CREATE UNIQUE INDEX "source_recipe_documents_revision_kind_key_idx" ON "source_recipe_documents" ("recipe_revision_id","document_kind","document_key");--> statement-breakpoint
486−CREATE INDEX "source_recipe_documents_revision_created_idx" ON "source_recipe_documents" ("recipe_revision_id","created_at","id");--> statement-breakpoint
487−CREATE UNIQUE INDEX "source_recipe_operations_revision_operation_key_idx" ON "source_recipe_operations" ("recipe_revision_id","operation_key");--> statement-breakpoint
488−CREATE INDEX "source_recipe_operations_revision_tool_idx" ON "source_recipe_operations" ("recipe_revision_id","tool_id","updated_at","id");--> statement-breakpoint
489−CREATE INDEX "source_recipe_operations_search_text_idx" ON "source_recipe_operations" USING gin (to_tsvector('simple', "search_text"));--> statement-breakpoint
490−CREATE UNIQUE INDEX "workspace_source_oauth_clients_workspace_source_provider_idx" ON "workspace_source_oauth_clients" ("workspace_id","source_id","provider_key");--> statement-breakpoint
491−CREATE INDEX "workspace_source_oauth_clients_workspace_idx" ON "workspace_source_oauth_clients" ("workspace_id","updated_at","source_id");
packages/control-plane/drizzle/20260310183000_source_recipe_schema_bundles/migration.sql+0−14
@@ -1,14 +0,0 @@
1−CREATE TABLE "source_recipe_schema_bundles" (
2− "id" text PRIMARY KEY NOT NULL,
3− "recipe_revision_id" text NOT NULL,
4− "bundle_kind" text NOT NULL,
5− "refs_json" text NOT NULL,
6− "content_hash" text NOT NULL,
7− "created_at" bigint NOT NULL,
8− "updated_at" bigint NOT NULL,
9− CONSTRAINT "source_recipe_schema_bundles_kind_check" CHECK ("source_recipe_schema_bundles"."bundle_kind" in ('json_schema_ref_map'))
10−);
11−--> statement-breakpoint
12−CREATE UNIQUE INDEX "source_recipe_schema_bundles_revision_kind_idx" ON "source_recipe_schema_bundles" USING btree ("recipe_revision_id","bundle_kind");
13−--> statement-breakpoint
14−CREATE INDEX "source_recipe_schema_bundles_revision_created_idx" ON "source_recipe_schema_bundles" USING btree ("recipe_revision_id","created_at","id");
packages/control-plane/drizzle/20260310223000_source_adapter_registry_strings/migration.sql+0−9
@@ -1,9 +0,0 @@
1−ALTER TABLE "sources" DROP CONSTRAINT IF EXISTS "sources_kind_check";
2−--> statement-breakpoint
3−ALTER TABLE "source_recipes" DROP CONSTRAINT IF EXISTS "source_recipes_importer_kind_check";
4−--> statement-breakpoint
5−ALTER TABLE "source_recipe_documents" DROP CONSTRAINT IF EXISTS "source_recipe_documents_kind_check";
6−--> statement-breakpoint
7−ALTER TABLE "source_recipe_schema_bundles" DROP CONSTRAINT IF EXISTS "source_recipe_schema_bundles_kind_check";
8−--> statement-breakpoint
9−ALTER TABLE "source_recipe_operations" DROP CONSTRAINT IF EXISTS "source_recipe_operations_provider_kind_check";
packages/control-plane/drizzle/20260310233000_source_adapter_families_and_data_migrations/migration.sql+0−23
@@ -1,23 +0,0 @@
1−ALTER TABLE "source_recipes"
2−DROP CONSTRAINT IF EXISTS "source_recipes_kind_check";--> statement-breakpoint
3−
4−UPDATE "source_recipes"
5−SET "kind" = 'http_api'
6−WHERE "kind" IN ('http_recipe', 'graphql_recipe');--> statement-breakpoint
7−
8−UPDATE "source_recipes"
9−SET "kind" = 'mcp'
10−WHERE "kind" = 'mcp_recipe';--> statement-breakpoint
11−
12−UPDATE "source_recipes"
13−SET "kind" = 'internal'
14−WHERE "kind" = 'internal_recipe';--> statement-breakpoint
15−
16−ALTER TABLE "source_recipes"
17−ADD CONSTRAINT "source_recipes_kind_check"
18−CHECK ("kind" in ('http_api', 'mcp', 'internal'));--> statement-breakpoint
19−
20−CREATE TABLE IF NOT EXISTS "control_plane_data_migrations" (
21− "id" text PRIMARY KEY NOT NULL,
22− "applied_at" bigint NOT NULL
23−);
packages/control-plane/drizzle/20260311010000_source_binding_configs/migration.sql+0−62
@@ -1,62 +0,0 @@
1−UPDATE "sources"
2−SET "binding_config_json" = CASE
3− WHEN "binding_config_json" IS NOT NULL AND btrim("binding_config_json") <> '' THEN "binding_config_json"
4− WHEN "kind" = 'openapi' THEN jsonb_build_object(
5− 'adapterKey', 'openapi',
6− 'specUrl', COALESCE("spec_url", "endpoint"),
7− 'defaultHeaders',
8− CASE
9− WHEN "default_headers_json" IS NULL OR btrim("default_headers_json") = '' THEN NULL
10− ELSE "default_headers_json"::jsonb
11− END
12− )::text
13− WHEN "kind" = 'graphql' THEN jsonb_build_object(
14− 'adapterKey', 'graphql',
15− 'defaultHeaders',
16− CASE
17− WHEN "default_headers_json" IS NULL OR btrim("default_headers_json") = '' THEN NULL
18− ELSE "default_headers_json"::jsonb
19− END
20− )::text
21− WHEN "kind" = 'mcp' THEN jsonb_build_object(
22− 'adapterKey', 'mcp',
23− 'transport', "transport",
24− 'queryParams',
25− CASE
26− WHEN "query_params_json" IS NULL OR btrim("query_params_json") = '' THEN NULL
27− ELSE "query_params_json"::jsonb
28− END,
29− 'headers',
30− CASE
31− WHEN "headers_json" IS NULL OR btrim("headers_json") = '' THEN NULL
32− ELSE "headers_json"::jsonb
33− END
34− )::text
35− ELSE jsonb_build_object(
36− 'adapterKey', "kind"
37− )::text
38−END;--> statement-breakpoint
39−
40−ALTER TABLE "sources"
41−ALTER COLUMN "binding_config_json" SET NOT NULL;--> statement-breakpoint
42−
43−ALTER TABLE "sources"
44−DROP CONSTRAINT IF EXISTS "sources_transport_check";--> statement-breakpoint
45−
46−ALTER TABLE "sources"
47−DROP COLUMN IF EXISTS "transport";--> statement-breakpoint
48−
49−ALTER TABLE "sources"
50−DROP COLUMN IF EXISTS "query_params_json";--> statement-breakpoint
51−
52−ALTER TABLE "sources"
53−DROP COLUMN IF EXISTS "headers_json";--> statement-breakpoint
54−
55−ALTER TABLE "sources"
56−DROP COLUMN IF EXISTS "spec_url";--> statement-breakpoint
57−
58−ALTER TABLE "sources"
59−DROP COLUMN IF EXISTS "default_headers_json";--> statement-breakpoint
60−
61−ALTER TABLE "sources"
62−DROP COLUMN IF EXISTS "source_document_text";
packages/control-plane/drizzle/20260311133000_import_auth_slots_and_materialization_hash/migration.sql+0−82
@@ -1,82 +0,0 @@
1−ALTER TABLE "sources" ADD COLUMN "import_auth_policy" text;--> statement-breakpoint
2−
3−UPDATE "sources"
4−SET "import_auth_policy" = CASE
5− WHEN "kind" = 'internal' THEN 'none'
6− ELSE 'reuse_runtime'
7−END
8−WHERE "import_auth_policy" IS NULL;--> statement-breakpoint
9−
10−ALTER TABLE "sources"
11−ALTER COLUMN "import_auth_policy" SET NOT NULL;--> statement-breakpoint
12−
13−ALTER TABLE "sources"
14−DROP CONSTRAINT IF EXISTS "sources_import_auth_policy_check";--> statement-breakpoint
15−
16−ALTER TABLE "sources"
17−ADD CONSTRAINT "sources_import_auth_policy_check"
18−CHECK ("import_auth_policy" in ('none', 'reuse_runtime', 'separate'));--> statement-breakpoint
19−
20−ALTER TABLE "source_recipe_revisions" ADD COLUMN "materialization_hash" text;--> statement-breakpoint
21−
22−UPDATE "source_recipe_revisions"
23−SET "materialization_hash" = "manifest_hash"
24−WHERE "materialization_hash" IS NULL;--> statement-breakpoint
25−
26−DROP INDEX IF EXISTS "source_recipe_revisions_recipe_manifest_idx";--> statement-breakpoint
27−
28−CREATE UNIQUE INDEX "source_recipe_revisions_recipe_materialization_idx"
29−ON "source_recipe_revisions" ("recipe_id","materialization_hash");--> statement-breakpoint
30−
31−CREATE INDEX "source_recipe_revisions_recipe_manifest_idx"
32−ON "source_recipe_revisions" ("recipe_id","manifest_hash");--> statement-breakpoint
33−
34−ALTER TABLE "workspace_source_credentials" ADD COLUMN "slot" text;--> statement-breakpoint
35−
36−UPDATE "workspace_source_credentials"
37−SET "slot" = 'runtime'
38−WHERE "slot" IS NULL;--> statement-breakpoint
39−
40−ALTER TABLE "workspace_source_credentials"
41−ALTER COLUMN "slot" SET NOT NULL;--> statement-breakpoint
42−
43−DROP INDEX IF EXISTS "credentials_workspace_source_actor_idx";--> statement-breakpoint
44−
45−ALTER TABLE "workspace_source_credentials"
46−DROP CONSTRAINT IF EXISTS "credentials_slot_check";--> statement-breakpoint
47−
48−ALTER TABLE "workspace_source_credentials"
49−ADD CONSTRAINT "credentials_slot_check"
50−CHECK ("slot" in ('runtime', 'import'));--> statement-breakpoint
51−
52−CREATE UNIQUE INDEX "credentials_workspace_source_actor_idx"
53−ON "workspace_source_credentials" ("workspace_id","source_id","actor_account_id","slot");--> statement-breakpoint
54−
55−ALTER TABLE "source_auth_sessions" ADD COLUMN "credential_slot" text;--> statement-breakpoint
56−
57−UPDATE "source_auth_sessions"
58−SET "credential_slot" = 'runtime'
59−WHERE "credential_slot" IS NULL;--> statement-breakpoint
60−
61−ALTER TABLE "source_auth_sessions"
62−ALTER COLUMN "credential_slot" SET NOT NULL;--> statement-breakpoint
63−
64−DROP INDEX IF EXISTS "source_auth_sessions_pending_idx";--> statement-breakpoint
65−
66−ALTER TABLE "source_auth_sessions"
67−DROP CONSTRAINT IF EXISTS "source_auth_sessions_credential_slot_check";--> statement-breakpoint
68−
69−ALTER TABLE "source_auth_sessions"
70−ADD CONSTRAINT "source_auth_sessions_credential_slot_check"
71−CHECK ("credential_slot" in ('runtime', 'import'));--> statement-breakpoint
72−
73−CREATE INDEX "source_auth_sessions_pending_idx"
74−ON "source_auth_sessions" (
75− "workspace_id",
76− "source_id",
77− "actor_account_id",
78− "credential_slot",
79− "status",
80− "updated_at",
81− "id"
82−);--> statement-breakpoint
packages/control-plane/drizzle/20260311170000_thin_source_recipe_operations/migration.sql+0−73
@@ -1,73 +0,0 @@
1−UPDATE "source_recipe_operations"
2−SET "provider_data_json" = CASE
3− WHEN "provider_kind" = 'openapi' THEN jsonb_strip_nulls(jsonb_build_object(
4− 'kind', 'openapi',
5− 'toolId', "tool_id",
6− 'rawToolId', COALESCE("openapi_raw_tool_id", "tool_id"),
7− 'operationId', "openapi_operation_id",
8− 'group', CASE
9− WHEN position('.' in "tool_id") > 0 THEN split_part("tool_id", '.', 1)
10− ELSE "tool_id"
11− END,
12− 'leaf', CASE
13− WHEN position('.' in "tool_id") > 0 THEN substring("tool_id" from position('.' in "tool_id") + 1)
14− ELSE "tool_id"
15− END,
16− 'tags', CASE
17− WHEN "openapi_tags_json" IS NOT NULL THEN "openapi_tags_json"::jsonb
18− ELSE '[]'::jsonb
19− END,
20− 'method', COALESCE("openapi_method", 'get'),
21− 'path', COALESCE("openapi_path_template", '/'),
22− 'operationHash', COALESCE(
23− "openapi_operation_hash",
24− md5(COALESCE("tool_id", '') || ':' || COALESCE("openapi_path_template", '/'))
25− ),
26− 'invocation', jsonb_build_object(
27− 'method', COALESCE("openapi_method", 'get'),
28− 'pathTemplate', COALESCE("openapi_path_template", '/'),
29− 'parameters', '[]'::jsonb,
30− 'requestBody', CASE
31− WHEN "openapi_request_body_required" IS NULL THEN null
32− ELSE jsonb_build_object(
33− 'required', "openapi_request_body_required",
34− 'contentTypes', '[]'::jsonb
35− )
36− END
37− )
38− ))::text
39− WHEN "provider_kind" = 'graphql' THEN jsonb_build_object(
40− 'kind', 'graphql',
41− 'toolKind', 'field',
42− 'toolId', "tool_id",
43− 'rawToolId', null,
44− 'group', null,
45− 'leaf', null,
46− 'fieldName', null,
47− 'operationType', "graphql_operation_type",
48− 'operationName', "graphql_operation_name",
49− 'operationDocument', null,
50− 'queryTypeName', null,
51− 'mutationTypeName', null,
52− 'subscriptionTypeName', null
53− )::text
54− WHEN "provider_kind" = 'mcp' THEN jsonb_build_object(
55− 'kind', 'mcp',
56− 'toolId', "tool_id",
57− 'toolName', COALESCE("mcp_tool_name", "title", "tool_id"),
58− 'description', "description"
59− )::text
60− ELSE "provider_data_json"
61−END
62−WHERE "provider_data_json" IS NULL;--> statement-breakpoint
63−
64−ALTER TABLE "source_recipe_operations" DROP COLUMN "mcp_tool_name";--> statement-breakpoint
65−ALTER TABLE "source_recipe_operations" DROP COLUMN "openapi_method";--> statement-breakpoint
66−ALTER TABLE "source_recipe_operations" DROP COLUMN "openapi_path_template";--> statement-breakpoint
67−ALTER TABLE "source_recipe_operations" DROP COLUMN "openapi_operation_hash";--> statement-breakpoint
68−ALTER TABLE "source_recipe_operations" DROP COLUMN "openapi_raw_tool_id";--> statement-breakpoint
69−ALTER TABLE "source_recipe_operations" DROP COLUMN "openapi_operation_id";--> statement-breakpoint
70−ALTER TABLE "source_recipe_operations" DROP COLUMN "openapi_tags_json";--> statement-breakpoint
71−ALTER TABLE "source_recipe_operations" DROP COLUMN "openapi_request_body_required";--> statement-breakpoint
72−ALTER TABLE "source_recipe_operations" DROP COLUMN "graphql_operation_type";--> statement-breakpoint
73−ALTER TABLE "source_recipe_operations" DROP COLUMN "graphql_operation_name";--> statement-breakpoint
packages/control-plane/drizzle/20260311223000_drop_tool_artifacts/migration.sql+0−7
@@ -1,7 +0,0 @@
1−DROP TABLE IF EXISTS "tool_artifact_parameters";
2−--> statement-breakpoint
3−DROP TABLE IF EXISTS "tool_artifact_request_body_content_types";
4−--> statement-breakpoint
5−DROP TABLE IF EXISTS "tool_artifact_ref_hint_keys";
6−--> statement-breakpoint
7−DROP TABLE IF EXISTS "tool_artifacts";
packages/control-plane/drizzle/20260312013000_adapter_keys_and_versioned_binding_configs/migration.sql+0−55
@@ -1,55 +0,0 @@
1−ALTER TABLE "source_recipes"
2−RENAME COLUMN "importer_kind" TO "adapter_key";--> statement-breakpoint
3−
4−ALTER TABLE "control_plane_data_migrations"
5−RENAME TO "control_plane_code_migrations";--> statement-breakpoint
6−
7−UPDATE "source_recipes"
8−SET "adapter_key" = CASE
9− WHEN "adapter_key" = 'graphql_introspection' THEN 'graphql'
10− WHEN "adapter_key" = 'mcp_manifest' THEN 'mcp'
11− ELSE "adapter_key"
12−END
13−WHERE "adapter_key" IN ('graphql_introspection', 'mcp_manifest');--> statement-breakpoint
14−
15−UPDATE "sources"
16−SET "binding_config_json" = jsonb_build_object(
17− 'adapterKey', "kind",
18− 'version', 1,
19− 'payload',
20− CASE
21− WHEN "kind" = 'openapi' THEN jsonb_build_object(
22− 'specUrl', COALESCE(("binding_config_json"::jsonb ->> 'specUrl'), "endpoint"),
23− 'defaultHeaders', COALESCE(("binding_config_json"::jsonb -> 'defaultHeaders'), 'null'::jsonb)
24− )
25− WHEN "kind" = 'graphql' THEN jsonb_build_object(
26− 'defaultHeaders', COALESCE(("binding_config_json"::jsonb -> 'defaultHeaders'), 'null'::jsonb)
27− )
28− WHEN "kind" = 'mcp' THEN jsonb_build_object(
29− 'transport', COALESCE(("binding_config_json"::jsonb -> 'transport'), 'null'::jsonb),
30− 'queryParams', COALESCE(("binding_config_json"::jsonb -> 'queryParams'), 'null'::jsonb),
31− 'headers', COALESCE(("binding_config_json"::jsonb -> 'headers'), 'null'::jsonb)
32− )
33− ELSE '{}'::jsonb
34− END
35−)::text
36−WHERE "binding_config_json" IS NOT NULL
37− AND (
38− ("binding_config_json"::jsonb -> 'version') IS NULL
39− OR ("binding_config_json"::jsonb -> 'payload') IS NULL
40− );--> statement-breakpoint
41−
42−UPDATE "sources"
43−SET "binding_config_json" = jsonb_set(
44− "binding_config_json"::jsonb,
45− '{adapterKey}',
46− to_jsonb(
47− CASE
48− WHEN "binding_config_json"::jsonb ->> 'adapterKey' = 'graphql_introspection' THEN 'graphql'
49− WHEN "binding_config_json"::jsonb ->> 'adapterKey' = 'mcp_manifest' THEN 'mcp'
50− ELSE "binding_config_json"::jsonb ->> 'adapterKey'
51− END
52− )
53−)::text
54−WHERE "binding_config_json" IS NOT NULL
55− AND ("binding_config_json"::jsonb ->> 'adapterKey') IN ('graphql_introspection', 'mcp_manifest');
packages/control-plane/drizzle/20260312113000_auth_artifacts_and_leases/migration.sql+0−132
@@ -1,132 +0,0 @@
1−ALTER TABLE "workspace_source_credentials"
2−RENAME TO "workspace_source_auth_artifacts";--> statement-breakpoint
3−
4−ALTER TABLE "workspace_source_auth_artifacts"
5−ADD COLUMN "artifact_kind" text;--> statement-breakpoint
6−
7−ALTER TABLE "workspace_source_auth_artifacts"
8−ADD COLUMN "config_json" text;--> statement-breakpoint
9−
10−ALTER TABLE "workspace_source_auth_artifacts"
11−ADD COLUMN "grant_set_json" text;--> statement-breakpoint
12−
13−UPDATE "workspace_source_auth_artifacts"
14−SET
15− "artifact_kind" = CASE
16− WHEN "auth_kind" = 'bearer' THEN 'static_bearer'
17− WHEN "auth_kind" = 'oauth2' THEN 'static_oauth2'
18− ELSE 'static_placements'
19− END,
20− "config_json" = CASE
21− WHEN "auth_kind" = 'bearer' THEN jsonb_build_object(
22− 'headerName', "auth_header_name",
23− 'prefix', "auth_prefix",
24− 'token', jsonb_build_object(
25− 'providerId', "token_provider_id",
26− 'handle', "token_handle"
27− )
28− )::text
29− WHEN "auth_kind" = 'oauth2' THEN jsonb_build_object(
30− 'headerName', "auth_header_name",
31− 'prefix', "auth_prefix",
32− 'accessToken', jsonb_build_object(
33− 'providerId', "token_provider_id",
34− 'handle', "token_handle"
35− ),
36− 'refreshToken',
37− CASE
38− WHEN "refresh_token_provider_id" IS NOT NULL AND "refresh_token_handle" IS NOT NULL
39− THEN jsonb_build_object(
40− 'providerId', "refresh_token_provider_id",
41− 'handle', "refresh_token_handle"
42− )
43− ELSE 'null'::jsonb
44− END
45− )::text
46− ELSE jsonb_build_object('placements', '[]'::jsonb)::text
47− END
48−WHERE "artifact_kind" IS NULL OR "config_json" IS NULL;--> statement-breakpoint
49−
50−ALTER TABLE "workspace_source_auth_artifacts"
51−ALTER COLUMN "artifact_kind" SET NOT NULL;--> statement-breakpoint
52−
53−ALTER TABLE "workspace_source_auth_artifacts"
54−ALTER COLUMN "config_json" SET NOT NULL;--> statement-breakpoint
55−
56−DROP INDEX IF EXISTS "credentials_workspace_idx";--> statement-breakpoint
57−DROP INDEX IF EXISTS "credentials_workspace_source_actor_idx";--> statement-breakpoint
58−DROP INDEX IF EXISTS "credentials_workspace_source_idx";--> statement-breakpoint
59−
60−ALTER TABLE "workspace_source_auth_artifacts"
61−DROP CONSTRAINT IF EXISTS "credentials_auth_kind_check";--> statement-breakpoint
62−
63−ALTER TABLE "workspace_source_auth_artifacts"
64−DROP CONSTRAINT IF EXISTS "credentials_slot_check";--> statement-breakpoint
65−
66−ALTER TABLE "workspace_source_auth_artifacts"
67−DROP COLUMN "auth_kind";--> statement-breakpoint
68−
69−ALTER TABLE "workspace_source_auth_artifacts"
70−DROP COLUMN "auth_header_name";--> statement-breakpoint
71−
72−ALTER TABLE "workspace_source_auth_artifacts"
73−DROP COLUMN "auth_prefix";--> statement-breakpoint
74−
75−ALTER TABLE "workspace_source_auth_artifacts"
76−DROP COLUMN "token_provider_id";--> statement-breakpoint
77−
78−ALTER TABLE "workspace_source_auth_artifacts"
79−DROP COLUMN "token_handle";--> statement-breakpoint
80−
81−ALTER TABLE "workspace_source_auth_artifacts"
82−DROP COLUMN "refresh_token_provider_id";--> statement-breakpoint
83−
84−ALTER TABLE "workspace_source_auth_artifacts"
85−DROP COLUMN "refresh_token_handle";--> statement-breakpoint
86−
87−ALTER TABLE "workspace_source_auth_artifacts"
88−ADD CONSTRAINT "auth_artifacts_slot_check"
89−CHECK ("slot" in ('runtime', 'import'));--> statement-breakpoint
90−
91−CREATE INDEX "auth_artifacts_workspace_idx"
92−ON "workspace_source_auth_artifacts" ("workspace_id","updated_at","id");--> statement-breakpoint
93−
94−CREATE UNIQUE INDEX "auth_artifacts_workspace_source_actor_idx"
95−ON "workspace_source_auth_artifacts" ("workspace_id","source_id","actor_account_id","slot");--> statement-breakpoint
96−
97−CREATE INDEX "auth_artifacts_workspace_source_idx"
98−ON "workspace_source_auth_artifacts" ("workspace_id","source_id","updated_at","id");--> statement-breakpoint
99−
100−CREATE TABLE "workspace_source_auth_leases" (
101− "id" text PRIMARY KEY NOT NULL,
102− "auth_artifact_id" text NOT NULL,
103− "workspace_id" text NOT NULL,
104− "source_id" text NOT NULL,
105− "actor_account_id" text,
106− "slot" text NOT NULL,
107− "placements_json" text NOT NULL,
108− "expires_at" bigint,
109− "refresh_after" bigint,
110− "created_at" bigint NOT NULL,
111− "updated_at" bigint NOT NULL
112−);--> statement-breakpoint
113−
114−ALTER TABLE "workspace_source_auth_leases"
115−ADD CONSTRAINT "auth_leases_slot_check"
116−CHECK ("slot" in ('runtime', 'import'));--> statement-breakpoint
117−
118−CREATE UNIQUE INDEX "auth_leases_auth_artifact_idx"
119−ON "workspace_source_auth_leases" ("auth_artifact_id");--> statement-breakpoint
120−
121−CREATE INDEX "auth_leases_workspace_source_idx"
122−ON "workspace_source_auth_leases" (
123− "workspace_id",
124− "source_id",
125− "actor_account_id",
126− "slot",
127− "updated_at",
128− "id"
129−);--> statement-breakpoint
130−
131−ALTER TABLE "source_auth_sessions"
132−DROP CONSTRAINT IF EXISTS "source_auth_sessions_provider_kind_check";--> statement-breakpoint
packages/control-plane/drizzle/20260312143000_auth_lease_template_placements/migration.sql+0−40
@@ -1,40 +0,0 @@
1−ALTER TABLE "workspace_source_auth_leases"
2−ADD COLUMN "placements_template_json" text;--> statement-breakpoint
3−
4−UPDATE "workspace_source_auth_leases"
5−SET "placements_template_json" = COALESCE(
6− (
7− SELECT jsonb_agg(
8− CASE
9− WHEN placement ->> 'location' = 'body' THEN jsonb_build_object(
10− 'location', 'body',
11− 'path', placement ->> 'path',
12− 'parts', jsonb_build_array(
13− jsonb_build_object(
14− 'kind', 'literal',
15− 'value', COALESCE(placement ->> 'value', '')
16− )
17− )
18− )
19− ELSE jsonb_build_object(
20− 'location', placement ->> 'location',
21− 'name', placement ->> 'name',
22− 'parts', jsonb_build_array(
23− jsonb_build_object(
24− 'kind', 'literal',
25− 'value', COALESCE(placement ->> 'value', '')
26− )
27− )
28− )
29− END
30− )::text
31− FROM jsonb_array_elements(COALESCE("placements_json"::jsonb, '[]'::jsonb)) AS placement
32− ),
33− '[]'
34−);--> statement-breakpoint
35−
36−ALTER TABLE "workspace_source_auth_leases"
37−ALTER COLUMN "placements_template_json" SET NOT NULL;--> statement-breakpoint
38−
39−ALTER TABLE "workspace_source_auth_leases"
40−DROP COLUMN "placements_json";--> statement-breakpoint
packages/control-plane/drizzle/20260312233000_execution_steps_and_private_interaction_responses/migration.sql+0−25
@@ -1,25 +0,0 @@
1−ALTER TABLE "execution_interactions"
2−ADD COLUMN "response_private_json" text;--> statement-breakpoint
3−
4−CREATE TABLE "execution_steps" (
5− "id" text PRIMARY KEY NOT NULL,
6− "execution_id" text NOT NULL,
7− "sequence" bigint NOT NULL,
8− "kind" text NOT NULL,
9− "status" text NOT NULL,
10− "path" text NOT NULL,
11− "args_json" text NOT NULL,
12− "result_json" text,
13− "error_text" text,
14− "interaction_id" text,
15− "created_at" bigint NOT NULL,
16− "updated_at" bigint NOT NULL,
17− CONSTRAINT "execution_steps_kind_check" CHECK ("execution_steps"."kind" in ('tool_call')),
18− CONSTRAINT "execution_steps_status_check" CHECK ("execution_steps"."status" in ('pending', 'waiting', 'completed', 'failed'))
19−);--> statement-breakpoint
20−
21−CREATE UNIQUE INDEX "execution_steps_execution_sequence_idx"
22−ON "execution_steps" ("execution_id", "sequence");--> statement-breakpoint
23−
24−CREATE INDEX "execution_steps_execution_updated_idx"
25−ON "execution_steps" ("execution_id", "updated_at", "id");
packages/control-plane/src/api/local/http.ts+5−5
@@ -12,7 +12,7 @@ import { · 11 unmodified lines
1212 import {
1313 ENV_SECRET_PROVIDER_ID,
1414 KEYCHAIN_SECRET_PROVIDER_ID,
15− POSTGRES_SECRET_PROVIDER_ID,
15+ LOCAL_SECRET_PROVIDER_ID,
1616 parseSecretStoreProviderId,
1717 } from "../../runtime/secret-material-providers";
1818 import { ControlPlaneStore } from "../../runtime/store";
@@ -35,8 +35,8 @@ const SECRET_STORE_PROVIDER_ENV = "EXECUTOR_SECRET_STORE_PROVIDER"; · 16 unmodified lines
3535 const getInstanceConfig = (): Effect.Effect<InstanceConfig> => {
3636 const providers: SecretProvider[] = [
3737 {
38− id: POSTGRES_SECRET_PROVIDER_ID,
39− name: "Database",
38+ id: LOCAL_SECRET_PROVIDER_ID,
39+ name: "Local store",
4040 canStore: true,
4141 },
4242 ];
@@ -57,7 +57,7 @@ const getInstanceConfig = (): Effect.Effect<InstanceConfig> => { · 14 unmodified lines
5757
5858 const defaultStoreProvider =
5959 parseSecretStoreProviderId(process.env[SECRET_STORE_PROVIDER_ENV])
60− ?? POSTGRES_SECRET_PROVIDER_ID;
60+ ?? LOCAL_SECRET_PROVIDER_ID;
6161
6262 return Effect.succeed({
6363 platform: process.platform,
@@ -133,7 +133,7 @@ export const ControlPlaneLocalLive = HttpApiBuilder.group( · 69 unmodified lines
133133 return {
134134 id,
135135 name,
136− providerId: POSTGRES_SECRET_PROVIDER_ID,
136+ providerId: LOCAL_SECRET_PROVIDER_ID,
137137 purpose,
138138 createdAt: now,
139139 updatedAt: now,
packages/control-plane/src/index.ts+0−1
@@ -1,5 +1,4 @@
11 export * from "./schema/index";
2−export * from "./persistence/index";
32 export * from "./api/index";
43 export * from "./runtime/index";
54 export * from "./client";
packages/control-plane/src/persistence/client.ts+0−70
@@ -1,70 +0,0 @@
1−import * as Effect from "effect/Effect";
2−
3−import {
4− type ControlPlanePersistenceError,
5− toPersistenceError,
6−} from "./persistence-errors";
7−import type { DrizzleDb, SqlBackend } from "./sql-runtime";
8−
9−export type DrizzleSession = Pick<DrizzleDb, "select" | "insert" | "update" | "delete">;
10−
11−export type DrizzleClient = {
12− backend: SqlBackend;
13− db: DrizzleDb;
14− use: <A>(
15− operation: string,
16− run: (db: DrizzleSession) => Promise<A>,
17− ) => Effect.Effect<A, ControlPlanePersistenceError>;
18− useTx: <A>(
19− operation: string,
20− run: (tx: DrizzleSession) => Promise<A>,
21− ) => Effect.Effect<A, ControlPlanePersistenceError>;
22−};
23−
24−const createSerializer = (backend: SqlBackend) => {
25− let queue = Promise.resolve<void>(undefined);
26−
27− return <A>(run: () => Promise<A>): Promise<A> => {
28− if (backend !== "pglite") {
29− return run();
30− }
31−
32− const next: Promise<A> = queue.then(run, run);
33− queue = next.then(
34− () => undefined,
35− () => undefined,
36− );
37− return next;
38− };
39−};
40−
41−export const createDrizzleClient = (input: {
42− backend: SqlBackend;
43− db: DrizzleDb;
44−}): DrizzleClient => {
45− const serialize = createSerializer(input.backend);
46−
47− const use = <A>(
48− operation: string,
49− run: (db: DrizzleSession) => Promise<A>,
50− ): Effect.Effect<A, ControlPlanePersistenceError> =>
51− Effect.tryPromise({
52− try: () => serialize(() => run(input.db)),
53− catch: (cause) => toPersistenceError(operation, cause),
54− });
55−
56− const useTx = <A>(
57− operation: string,
58− run: (tx: DrizzleSession) => Promise<A>,
59− ): Effect.Effect<A, ControlPlanePersistenceError> =>
60− use(operation, () =>
61− input.db.transaction(async (tx) => run(tx)),
62− );
63−
64− return {
65− backend: input.backend,
66− db: input.db,
67− use,
68− useTx,
69− };
70−};
packages/control-plane/src/persistence/code-migrations.ts+0−234
@@ -1,234 +0,0 @@
1−import {
2− type McpSourceAuthSessionData,
3− type SourceAuthSession,
4− type StoredSourceRecipeDocumentRecord,
5− type StoredSourceRecord,
6−} from "#schema";
7−import {
8− JsonObjectSchema,
9− McpSourceAuthSessionDataJsonSchema,
10−} from "#schema";
11−import * as Either from "effect/Either";
12−import * as Effect from "effect/Effect";
13−import * as ParseResult from "effect/ParseResult";
14−import * as Schema from "effect/Schema";
15−
16−import { rebuildGraphqlRecipeRevisionContent } from "../runtime/source-adapters/graphql";
17−import { rebuildOpenApiRecipeRevisionContent } from "../runtime/source-adapters/openapi";
18−import type { SqlControlPlaneRows } from "./index";
19−
20−type DataMigration = {
21− id: string;
22− run: (rows: SqlControlPlaneRows) => Effect.Effect<void, Error, never>;
23−};
24−
25−const LegacyMcpSourceAuthSessionDataSchema = Schema.Struct({
26− kind: Schema.Literal("mcp_oauth"),
27− endpoint: Schema.String,
28− redirectUri: Schema.String,
29− scope: Schema.NullOr(Schema.String),
30− resourceMetadataUrl: Schema.NullOr(Schema.String),
31− authorizationServerUrl: Schema.NullOr(Schema.String),
32− resourceMetadataJson: Schema.NullOr(Schema.String),
33− authorizationServerMetadataJson: Schema.NullOr(Schema.String),
34− clientInformationJson: Schema.NullOr(Schema.String),
35− codeVerifier: Schema.NullOr(Schema.String),
36− authorizationUrl: Schema.NullOr(Schema.String),
37−});
38−
39−const decodeLegacyMcpSourceAuthSessionDataJson = Schema.decodeUnknownEither(
40− Schema.parseJson(LegacyMcpSourceAuthSessionDataSchema),
41−);
42−
43−const decodeMcpSourceAuthSessionDataJson = Schema.decodeUnknownEither(
44− McpSourceAuthSessionDataJsonSchema,
45−);
46−
47−const encodeMcpSourceAuthSessionDataJson = Schema.encodeSync(
48− McpSourceAuthSessionDataJsonSchema,
49−);
50−
51−const decodeJsonObjectFromJson = Schema.decodeUnknownEither(
52− Schema.parseJson(JsonObjectSchema),
53−);
54−
55−const decodeLegacyJsonObject = (value: string | null) => {
56− if (value === null) {
57− return null;
58− }
59−
60− const decoded = decodeJsonObjectFromJson(value);
61− return Either.isRight(decoded) ? decoded.right : null;
62−};
63−
64−const repairLegacyMcpSourceAuthSession = (input: {
65− rows: SqlControlPlaneRows;
66− session: SourceAuthSession;
67−}): Effect.Effect<void, never, never> => {
68− if (input.session.providerKind !== "mcp_oauth") {
69− return Effect.void;
70− }
71−
72− if (
73− Either.isRight(
74− decodeMcpSourceAuthSessionDataJson(input.session.sessionDataJson),
75− )
76− ) {
77− return Effect.void;
78− }
79−
80− const legacy = decodeLegacyMcpSourceAuthSessionDataJson(
81− input.session.sessionDataJson,
82− );
83− if (Either.isLeft(legacy)) {
84− console.warn(
85− `Skipping source auth session data migration for ${input.session.id}: ${ParseResult.TreeFormatter.formatErrorSync(legacy.left)}`,
86− );
87− return Effect.void;
88− }
89−
90− const repaired: McpSourceAuthSessionData = {
91− kind: "mcp_oauth",
92− endpoint: legacy.right.endpoint,
93− redirectUri: legacy.right.redirectUri,
94− scope: legacy.right.scope,
95− resourceMetadataUrl: legacy.right.resourceMetadataUrl,
96− authorizationServerUrl: legacy.right.authorizationServerUrl,
97− resourceMetadata: decodeLegacyJsonObject(legacy.right.resourceMetadataJson),
98− authorizationServerMetadata: decodeLegacyJsonObject(
99− legacy.right.authorizationServerMetadataJson,
100− ),
101− clientInformation: decodeLegacyJsonObject(
102− legacy.right.clientInformationJson,
103− ),
104− codeVerifier: legacy.right.codeVerifier,
105− authorizationUrl: legacy.right.authorizationUrl,
106− };
107−
108− return input.rows.sourceAuthSessions
109− .update(input.session.id, {
110− sessionDataJson: encodeMcpSourceAuthSessionDataJson(repaired),
111− })
112− .pipe(Effect.asVoid, Effect.orDie);
113−};
114−
115−const migrateLegacySourceAuthSessions: DataMigration = {
116− id: "20260310_repair_legacy_mcp_oauth_sessions",
117− run: (rows) =>
118− Effect.gen(function* () {
119− const sourceAuthSessions = yield* rows.sourceAuthSessions.listAll();
120− yield* Effect.forEach(
121− sourceAuthSessions,
122− (session) =>
123− repairLegacyMcpSourceAuthSession({
124− rows,
125− session,
126− }),
127− { discard: true },
128− );
129− }),
130−};
131−
132−const rebuildPersistedSourceRecipes: DataMigration = {
133− id: "20260310_rebuild_persisted_source_recipes",
134− run: (rows) =>
135− Effect.gen(function* () {
136− const sourceRecords = yield* rows.sources.listAll();
137− if (sourceRecords.length === 0) {
138− return;
139− }
140−
141− const revisionIds = [
142− ...new Set(sourceRecords.map((sourceRecord) => sourceRecord.recipeRevisionId)),
143− ];
144− const [revisions, documents] = yield* Effect.all([
145− rows.sourceRecipeRevisions.listByIds(revisionIds),
146− rows.sourceRecipeDocuments.listByRevisionIds(revisionIds),
147− ]);
148−
149− const revisionById = new Map(
150− revisions.map((revision) => [revision.id, revision]),
151− );
152− const sourceRecordByRevisionId = new Map<string, StoredSourceRecord>();
153− for (const sourceRecord of sourceRecords) {
154− if (!sourceRecordByRevisionId.has(sourceRecord.recipeRevisionId)) {
155− sourceRecordByRevisionId.set(sourceRecord.recipeRevisionId, sourceRecord);
156− }
157− }
158−
159− const documentsByRevisionId = new Map<string, StoredSourceRecipeDocumentRecord[]>();
160− for (const document of documents) {
161− const existing = documentsByRevisionId.get(document.recipeRevisionId) ?? [];
162− existing.push(document);
163− documentsByRevisionId.set(document.recipeRevisionId, existing);
164− }
165−
166− yield* Effect.forEach(
167− revisionIds,
168− (revisionId) =>
169− Effect.gen(function* () {
170− const sourceRecord = sourceRecordByRevisionId.get(revisionId);
171− const revision = revisionById.get(revisionId);
172− const documentsForRevision = documentsByRevisionId.get(revisionId) ?? [];
173− if (!sourceRecord || !revision) {
174− return;
175− }
176−
177− if (sourceRecord.kind === "openapi") {
178− if (documentsForRevision.length === 0) {
179− return;
180− }
181− yield* rebuildOpenApiRecipeRevisionContent({
182− rows,
183− sourceRecord,
184− revision,
185− documents: documentsForRevision,
186− });
187− return;
188− }
189−
190− if (sourceRecord.kind !== "graphql") {
191− return;
192− }
193−
194− if (documentsForRevision.length === 0) {
195− return;
196− }
197−
198− yield* rebuildGraphqlRecipeRevisionContent({
199− rows,
200− sourceRecord,
201− revision,
202− documents: documentsForRevision,
203− });
204− }),
205− { discard: true },
206− );
207− }),
208−};
209−
210−const codeMigrations = [
211− rebuildPersistedSourceRecipes,
212− migrateLegacySourceAuthSessions,
213−] as const satisfies readonly DataMigration[];
214−
215−export const runCodeMigrations = (
216− rows: SqlControlPlaneRows,
217−): Effect.Effect<void, Error, never> =>
218− Effect.gen(function* () {
219− const applied = new Set(
220− (yield* rows.codeMigrations.listAll()).map((migration) => migration.id),
221− );
222−
223− for (const migration of codeMigrations) {
224− if (applied.has(migration.id)) {
225− continue;
226− }
227−
228− yield* migration.run(rows);
229− yield* rows.codeMigrations.upsert({
230− id: migration.id,
231− appliedAt: Date.now(),
232− });
233− }
234− });
packages/control-plane/src/persistence/index.ts+0−182
@@ -1,182 +0,0 @@
1−import * as Context from "effect/Context";
2−import * as Data from "effect/Data";
3−import * as Effect from "effect/Effect";
4−import * as Layer from "effect/Layer";
5−
6−import { createDrizzleClient, type DrizzleClient } from "./client";
7−import {
8− createAccountsRepo,
9− createAuthArtifactsRepo,
10− createAuthLeasesRepo,
11− createCodeMigrationsRepo,
12− createExecutionInteractionsRepo,
13− createExecutionStepsRepo,
14− createExecutionsRepo,
15− createSecretMaterialsRepo,
16− createSourceAuthSessionsRepo,
17− createSourceOauthClientsRepo,
18− createSourceRecipeDocumentsRepo,
19− createSourceRecipeSchemaBundlesRepo,
20− createSourceRecipeOperationsRepo,
21− createSourceRecipeRevisionsRepo,
22− createSourceRecipesRepo,
23− createSourcesRepo,
24−} from "./repos";
25−import { drizzleSchema, tableNames, type DrizzleTables } from "./schema";
26−import {
27− createSqlRuntime,
28− runMigrations,
29− type CreateSqlRuntimeOptions,
30− type DrizzleDb,
31− type SqlBackend,
32− type SqlRuntime,
33−} from "./sql-runtime";
34−import { runCodeMigrations } from "./code-migrations";
35−
36−export { tableNames, type DrizzleTables } from "./schema";
37−export {
38− ControlPlanePersistenceError,
39− toPersistenceError,
40− type PersistenceErrorKind,
41−} from "./persistence-errors";
42−export { createDrizzleClient, type DrizzleClient } from "./client";
43−export {
44− createSqlRuntime,
45− runMigrations,
46− type SqlRuntime,
47− type SqlBackend,
48− type DrizzleDb,
49− type CreateSqlRuntimeOptions,
50−} from "./sql-runtime";
51−
52−const createRows = (client: DrizzleClient, tables: DrizzleTables = drizzleSchema) => ({
53− accounts: createAccountsRepo(client, tables),
54− sources: createSourcesRepo(client, tables),
55− sourceRecipes: createSourceRecipesRepo(client, tables),
56− sourceRecipeRevisions: createSourceRecipeRevisionsRepo(client, tables),
57− sourceRecipeDocuments: createSourceRecipeDocumentsRepo(client, tables),
58− sourceRecipeSchemaBundles: createSourceRecipeSchemaBundlesRepo(client, tables),
59− sourceRecipeOperations: createSourceRecipeOperationsRepo(client, tables),
60− codeMigrations: createCodeMigrationsRepo(client, tables),
61− authArtifacts: createAuthArtifactsRepo(client, tables),
62− authLeases: createAuthLeasesRepo(client, tables),
63− sourceOauthClients: createSourceOauthClientsRepo(client, tables),
64− secretMaterials: createSecretMaterialsRepo(client, tables),
65− sourceAuthSessions: createSourceAuthSessionsRepo(client, tables),
66− executions: createExecutionsRepo(client, tables),
67− executionInteractions: createExecutionInteractionsRepo(client, tables),
68− executionSteps: createExecutionStepsRepo(client, tables),
69−});
70−
71−export type SqlControlPlaneRows = ReturnType<typeof createRows>;
72−
73−export type SqlControlPlanePersistence = {
74− backend: SqlBackend;
75− db: DrizzleDb;
76− rows: SqlControlPlaneRows;
77− close: () => Promise<void>;
78−};
79−
80−export class SqlControlPlanePersistenceService extends Context.Tag(
81− "#persistence/SqlControlPlanePersistenceService",
82−)<SqlControlPlanePersistenceService, SqlControlPlanePersistence>() {}
83−
84−export class SqlControlPlaneRowsService extends Context.Tag(
85− "#persistence/SqlControlPlaneRowsService",
86−)<SqlControlPlaneRowsService, SqlControlPlaneRows>() {}
87−
88−export class SqlPersistenceBootstrapError extends Data.TaggedError(
89− "SqlPersistenceBootstrapError",
90−)<{
91− message: string;
92− details: string | null;
93−}> {}
94−
95−const toBootstrapError = (cause: unknown): SqlPersistenceBootstrapError => {
96− const details = cause instanceof Error ? cause.message : String(cause);
97− return new SqlPersistenceBootstrapError({
98− message: `Failed initializing SQL control-plane persistence: ${details}`,
99− details,
100− });
101−};
102−
103−const createRuntimeEffect = (options: CreateSqlRuntimeOptions) =>
104− Effect.tryPromise({
105− try: () => createSqlRuntime(options),
106− catch: toBootstrapError,
107− });
108−
109−const runMigrationsEffect = (
110− runtime: SqlRuntime,
111− migrationsFolder: string | undefined,
112−) =>
113− Effect.tryPromise({
114− try: () => runMigrations(runtime, { migrationsFolder }),
115− catch: toBootstrapError,
116− });
117−
118−const closeRuntimeEffect = (runtime: SqlRuntime) =>
119− Effect.tryPromise({
120− try: () => runtime.close(),
121− catch: (cause) =>
122− cause instanceof Error ? cause : new Error(String(cause ?? "unknown close error")),
123− }).pipe(Effect.orDie);
124−
125−export type CreateSqlControlPlanePersistenceOptions =
126− & CreateSqlRuntimeOptions
127− & {
128− runCodeMigrations?: boolean;
129− };
130−
131−export const createSqlControlPlanePersistence = (
132− options: CreateSqlControlPlanePersistenceOptions,
133−): Effect.Effect<SqlControlPlanePersistence, SqlPersistenceBootstrapError> =>
134− Effect.flatMap(createRuntimeEffect(options), (runtime) =>
135− runMigrationsEffect(runtime, options.migrationsFolder).pipe(
136− Effect.map(() => {
137− const client = createDrizzleClient({
138− backend: runtime.backend,
139− db: runtime.db,
140− });
141− const rows = createRows(client);
142−
143− return {
144− backend: runtime.backend,
145− db: runtime.db,
146− rows,
147− close: () => runtime.close(),
148− } satisfies SqlControlPlanePersistence;
149− }),
150− Effect.flatMap((persistence) =>
151− options.runCodeMigrations === false
152− ? Effect.succeed(persistence)
153− : runCodeMigrations(persistence.rows).pipe(
154− Effect.mapError(toBootstrapError),
155− Effect.map(() => persistence),
156− )),
157− Effect.catchAll((error) =>
158− closeRuntimeEffect(runtime).pipe(
159− Effect.zipRight(Effect.fail(error)),
160− )),
161− ));
162−
163−export const SqlControlPlanePersistenceLive = (
164− options: CreateSqlControlPlanePersistenceOptions,
165−) =>
166− Layer.scoped(
167− SqlControlPlanePersistenceService,
168− Effect.acquireRelease(
169− createSqlControlPlanePersistence(options),
170− (persistence) =>
171− Effect.tryPromise({
172− try: () => persistence.close(),
173− catch: (cause) =>
174− cause instanceof Error ? cause : new Error(String(cause ?? "unknown close error")),
175− }).pipe(Effect.orDie),
176− ),
177− );
178−
179−export const SqlControlPlaneRowsLive = Layer.effect(
180− SqlControlPlaneRowsService,
181− Effect.map(SqlControlPlanePersistenceService, (persistence) => persistence.rows),
182−);
packages/control-plane/src/persistence/persistence-errors.ts+0−92
@@ -1,92 +0,0 @@
1−import * as Data from "effect/Data";
2−
3−export type PersistenceErrorKind =
4− | "unique_violation"
5− | "not_null_violation"
6− | "check_violation"
7− | "foreign_key_violation"
8− | "serialization_failure"
9− | "unknown";
10−
11−type ErrorMetadata = {
12− code: string | null;
13− constraint: string | null;
14− table: string | null;
15− details: string | null;
16− message: string;
17− kind: PersistenceErrorKind;
18−};
19−
20−export class ControlPlanePersistenceError extends Data.TaggedError(
21− "ControlPlanePersistenceError",
22−)<{
23− operation: string;
24− message: string;
25− details: string | null;
26− code: string | null;
27− constraint: string | null;
28− table: string | null;
29− kind: PersistenceErrorKind;
30− cause: unknown;
31−}> {}
32−
33−const asRecord = (value: unknown): Record<string, unknown> | null =>
34− typeof value === "object" && value !== null ? (value as Record<string, unknown>) : null;
35−
36−const readString = (value: unknown): string | null =>
37− typeof value === "string" && value.length > 0 ? value : null;
38−
39−const classifyKind = (code: string | null): PersistenceErrorKind => {
40− switch (code) {
41− case "23505":
42− return "unique_violation";
43− case "23502":
44− return "not_null_violation";
45− case "23514":
46− return "check_violation";
47− case "23503":
48− return "foreign_key_violation";
49− case "40001":
50− return "serialization_failure";
51− default:
52− return "unknown";
53− }
54−};
55−
56−const toErrorMetadata = (cause: unknown): ErrorMetadata => {
57− const record = asRecord(cause);
58− const code = readString(record?.code);
59− const constraint =
60− readString(record?.constraint)
61− ?? readString(record?.constraint_name)
62− ?? readString(record?.constraintName);
63− const table = readString(record?.table) ?? readString(record?.table_name);
64− const details = readString(record?.detail) ?? readString(record?.details);
65− const message = cause instanceof Error ? cause.message : String(cause);
66−
67− return {
68− code,
69− constraint,
70− table,
71− details,
72− message,
73− kind: classifyKind(code),
74− };
75−};
76−
77−export const toPersistenceError = (
78− operation: string,
79− cause: unknown,
80−): ControlPlanePersistenceError => {
81− const metadata = toErrorMetadata(cause);
82− return new ControlPlanePersistenceError({
83− operation,
84− message: `Control-plane persistence failed during ${operation}`,
85− details: metadata.details ?? metadata.message,
86− code: metadata.code,
87− constraint: metadata.constraint,
88− table: metadata.table,
89− kind: metadata.kind,
90− cause,
91− });
92−};
packages/control-plane/src/persistence/post-migrations.test.ts+0−915
@@ -1,915 +0,0 @@
1−import { cpSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
2−import { tmpdir } from "node:os";
3−import path from "node:path";
4−import { fileURLToPath } from "node:url";
5−
6−import { describe, expect, it } from "@effect/vitest";
7−import {
8− AccountIdSchema,
9− McpSourceAuthSessionDataJsonSchema,
10− SourceAuthSessionIdSchema,
11− decodeAuthLeasePlacementTemplates,
12− SourceIdSchema,
13− SourceRecipeIdSchema,
14− SourceRecipeRevisionIdSchema,
15− WorkspaceIdSchema,
16−} from "#schema";
17−import * as Effect from "effect/Effect";
18−import * as Option from "effect/Option";
19−import * as Schema from "effect/Schema";
20−import * as Scope from "effect/Scope";
21−import { buildSchema, getIntrospectionQuery, graphqlSync } from "graphql";
22−import { sql } from "drizzle-orm";
23−
24−import {
25− createSqlControlPlanePersistence,
26− type SqlControlPlanePersistence,
27−} from "./index";
28−import { runCodeMigrations } from "./code-migrations";
29−
30−const openApiBindingConfigJson = (specUrl: string): string =>
31− JSON.stringify({
32− adapterKey: "openapi",
33− version: 1,
34− payload: {
35− specUrl,
36− defaultHeaders: null,
37− },
38− });
39−
40−const graphqlBindingConfigJson = (): string =>
41− JSON.stringify({
42− adapterKey: "graphql",
43− version: 1,
44− payload: {
45− defaultHeaders: null,
46− },
47− });
48−
49−const baseRevisionRecord = (input: {
50− id: ReturnType<typeof SourceRecipeRevisionIdSchema.make>;
51− recipeId: ReturnType<typeof SourceRecipeIdSchema.make>;
52− revisionNumber: number;
53− sourceConfigJson: string;
54− manifestJson?: string | null;
55− manifestHash?: string | null;
56− materializationHash?: string | null;
57− createdAt: number;
58− updatedAt: number;
59−}) => ({
60− id: input.id,
61− recipeId: input.recipeId,
62− revisionNumber: input.revisionNumber,
63− sourceConfigJson: input.sourceConfigJson,
64− manifestJson: input.manifestJson ?? null,
65− manifestHash: input.manifestHash ?? null,
66− materializationHash: input.materializationHash ?? null,
67− createdAt: input.createdAt,
68− updatedAt: input.updatedAt,
69−});
70−
71−const drizzleDir = path.resolve(
72− path.dirname(fileURLToPath(import.meta.url)),
73− "../../drizzle",
74−);
75−
76−const copyMigrationsBefore = (cutoffDirectoryName: string): string => {
77− const targetDir = mkdtempSync(path.join(tmpdir(), "executor-cp-migrations-"));
78− for (const directoryName of readdirSync(drizzleDir).sort()) {
79− if (directoryName >= cutoffDirectoryName) {
80− continue;
81− }
82−
83− cpSync(
84− path.join(drizzleDir, directoryName),
85− path.join(targetDir, directoryName),
86− { recursive: true },
87− );
88− }
89−
90− return targetDir;
91−};
92−
93−const makePersistence: Effect.Effect<
94− SqlControlPlanePersistence,
95− unknown,
96− Scope.Scope
97−> = Effect.acquireRelease(
98− createSqlControlPlanePersistence({
99− localDataDir: ":memory:",
100− }),
101− (persistence) =>
102− Effect.tryPromise({
103− try: () => persistence.close(),
104− catch: (cause) =>
105− cause instanceof Error ? cause : new Error(String(cause)),
106− }).pipe(Effect.orDie),
107−);
108−
109−const seedMigratedSourceRecipe = (input: {
110− persistence: SqlControlPlanePersistence;
111− kind: "openapi" | "graphql";
112− workspaceId: ReturnType<typeof WorkspaceIdSchema.make>;
113− sourceId: ReturnType<typeof SourceIdSchema.make>;
114− documentText: string;
115−}): Effect.Effect<
116− {
117− recipeRevisionId: ReturnType<typeof SourceRecipeRevisionIdSchema.make>;
118− },
119− unknown,
120− never
121−> =>
122− Effect.gen(function* () {
123− const now = Date.now();
124− const accountId = AccountIdSchema.make(`acc_${input.sourceId}`);
125− const recipeId = SourceRecipeIdSchema.make(`src_recipe_${input.sourceId}`);
126− const recipeRevisionId = SourceRecipeRevisionIdSchema.make(
127− `src_recipe_rev_${input.sourceId}`,
128− );
129−
130− yield* input.persistence.rows.sourceRecipes.upsert({
131− id: recipeId,
132− kind: "http_api",
133− adapterKey:
134− input.kind === "openapi" ? "openapi" : "graphql",
135− providerKey:
136− input.kind === "openapi" ? "generic_http" : "generic_graphql",
137− name: input.kind === "openapi" ? "GitHub" : "GraphQL Demo",
138− summary: null,
139− visibility: "workspace",
140− latestRevisionId: recipeRevisionId,
141− createdAt: now,
142− updatedAt: now,
143− });
144− yield* input.persistence.rows.sourceRecipeRevisions.upsert(baseRevisionRecord({
145− id: recipeRevisionId,
146− recipeId,
147− revisionNumber: 1,
148− sourceConfigJson: JSON.stringify(
149− input.kind === "openapi"
150− ? {
151− kind: "openapi",
152− endpoint: "https://api.example.com",
153− specUrl: "https://api.example.com/openapi.json",
154− defaultHeaders: null,
155− }
156− : {
157− kind: "graphql",
158− endpoint: "https://api.example.com/graphql",
159− defaultHeaders: null,
160− },
161− ),
162− manifestJson: null,
163− manifestHash: null,
164− materializationHash: null,
165− createdAt: now,
166− updatedAt: now,
167− }));
168− yield* input.persistence.rows.sources.insert({
169− id: input.sourceId,
170− workspaceId: input.workspaceId,
171− recipeId,
172− recipeRevisionId,
173− name: input.kind === "openapi" ? "GitHub" : "GraphQL Demo",
174− kind: input.kind,
175− endpoint:
176− input.kind === "openapi"
177− ? "https://api.example.com"
178− : "https://api.example.com/graphql",
179− status: "connected",
180− enabled: true,
181− namespace: input.kind === "openapi" ? "github" : "graphql",
182− importAuthPolicy: "reuse_runtime",
183− bindingConfigJson:
184− input.kind === "openapi"
185− ? openApiBindingConfigJson("https://api.example.com/openapi.json")
186− : graphqlBindingConfigJson(),
187− sourceHash: null,
188− lastError: null,
189− createdAt: now,
190− updatedAt: now,
191− });
192− yield* input.persistence.rows.sourceRecipeDocuments.replaceForRevision({
193− recipeRevisionId,
194− documents: [
195− {
196− id: `src_recipe_doc_${input.sourceId}`,
197− recipeRevisionId,
198− documentKind:
199− input.kind === "openapi" ? "openapi" : "graphql_introspection",
200− documentKey:
201− input.kind === "openapi"
202− ? "https://api.example.com/openapi.json"
203− : "https://api.example.com/graphql",
204− contentText: input.documentText,
205− contentHash: `hash_${input.sourceId}`,
206− fetchedAt: now,
207− createdAt: now,
208− updatedAt: now,
209− },
210− ],
211− });
212−
213− return { recipeRevisionId };
214− });
215−
216−describe("code-migrations", () => {
217− it.scoped("repairs migrated OpenAPI recipes from stored documents", () =>
218− Effect.gen(function* () {
219− const persistence = yield* makePersistence;
220− const workspaceId = WorkspaceIdSchema.make("ws_post_migration_openapi");
221− const sourceId = SourceIdSchema.make("src_post_migration_openapi");
222− yield* persistence.rows.codeMigrations.clearAll();
223−
224− const openApiDocument = JSON.stringify({
225− openapi: "3.0.3",
226− info: {
227− title: "GitHub",
228− version: "1.0.0",
229− },
230− paths: {
231− "/repos/{owner}/{repo}": {
232− get: {
233− operationId: "repos.getRepo",
234− parameters: [
235− {
236− name: "owner",
237− in: "path",
238− required: true,
239− schema: { type: "string" },
240− },
241− {
242− name: "repo",
243− in: "path",
244− required: true,
245− schema: { type: "string" },
246− },
247− ],
248− responses: {
249− 200: {
250− description: "ok",
251− content: {
252− "application/json": {
253− schema: {
254− $ref: "#/components/schemas/Repository",
255− },
256− },
257− },
258− },
259− },
260− },
261− },
262− },
263− components: {
264− schemas: {
265− Repository: {
266− type: "object",
267− properties: {
268− full_name: { type: "string" },
269− },
270− required: ["full_name"],
271− },
272− },
273− },
274− });
275−
276− const { recipeRevisionId } = yield* seedMigratedSourceRecipe({
277− persistence,
278− kind: "openapi",
279− workspaceId,
280− sourceId,
281− documentText: openApiDocument,
282− });
283−
284− yield* runCodeMigrations(persistence.rows);
285−
286− const revision =
287− yield* persistence.rows.sourceRecipeRevisions.getById(recipeRevisionId);
288− expect(Option.isSome(revision)).toBe(true);
289− expect(revision.pipe(Option.getOrNull)?.manifestJson).not.toBeNull();
290− expect(
291− (yield* persistence.rows.sourceRecipeSchemaBundles.listByRevisionId(
292− recipeRevisionId,
293− )).length,
294− ).toBe(1);
295− expect(
296− (yield* persistence.rows.sourceRecipeOperations.listByRevisionId(
297− recipeRevisionId,
298− )).length,
299− ).toBeGreaterThan(0);
300− }),
301− 60_000,
302− );
303−
304− it.scoped("repairs migrated GraphQL recipes from stored documents", () =>
305− Effect.gen(function* () {
306− const persistence = yield* makePersistence;
307− const workspaceId = WorkspaceIdSchema.make("ws_post_migration_graphql");
308− const sourceId = SourceIdSchema.make("src_post_migration_graphql");
309− yield* persistence.rows.codeMigrations.clearAll();
310− const schema = buildSchema(`
311− type Query {
312− viewer: User!
313− }
314−
315− type Mutation {
316− createIssue(title: String!): Issue!
317− }
318−
319− type User {
320− login: String!
321− }
322−
323− type Issue {
324− id: ID!
325− title: String!
326− }
327− `);
328− const graphqlDocument = JSON.stringify(
329− graphqlSync({
330− schema,
331− source: getIntrospectionQuery(),
332− }),
333− );
334−
335− const { recipeRevisionId } = yield* seedMigratedSourceRecipe({
336− persistence,
337− kind: "graphql",
338− workspaceId,
339− sourceId,
340− documentText: graphqlDocument,
341− });
342−
343− yield* runCodeMigrations(persistence.rows);
344−
345− const revision =
346− yield* persistence.rows.sourceRecipeRevisions.getById(recipeRevisionId);
347− expect(Option.isSome(revision)).toBe(true);
348− expect(revision.pipe(Option.getOrNull)?.manifestJson).not.toBeNull();
349− expect(
350− (yield* persistence.rows.sourceRecipeSchemaBundles.listByRevisionId(
351− recipeRevisionId,
352− )).length,
353− ).toBe(1);
354− expect(
355− (yield* persistence.rows.sourceRecipeOperations.listByRevisionId(
356− recipeRevisionId,
357− )).length,
358− ).toBeGreaterThan(0);
359− }),
360− 60_000,
361− );
362−
363− it.scoped("repairs legacy MCP source auth session payloads", () =>
364− Effect.gen(function* () {
365− const persistence = yield* makePersistence;
366− const workspaceId = WorkspaceIdSchema.make("ws_post_migration_session");
367− const sourceId = SourceIdSchema.make("src_post_migration_session");
368− yield* persistence.rows.codeMigrations.clearAll();
369− const sessionId = SourceAuthSessionIdSchema.make(
370− "src_auth_post_migration_session",
371− );
372− const now = Date.now();
373−
374− yield* persistence.rows.sourceAuthSessions.insert({
375− id: sessionId,
376− workspaceId,
377− sourceId,
378− actorAccountId: null,
379− executionId: null,
380− interactionId: null,
381− providerKind: "mcp_oauth",
382− credentialSlot: "runtime",
383− status: "pending",
384− state: "state_post_migration_session",
385− sessionDataJson: JSON.stringify({
386− kind: "mcp_oauth",
387− endpoint: "https://api.github.com",
388− redirectUri: "http://127.0.0.1/callback",
389− scope: null,
390− resourceMetadataUrl:
391− "https://api.github.com/.well-known/oauth-protected-resource",
392− authorizationServerUrl: "https://github.com/login/oauth",
393− resourceMetadataJson: '{"issuer":"https://api.github.com"}',
394− authorizationServerMetadataJson:
395− '{"token_endpoint":"https://github.com/login/oauth/access_token"}',
396− clientInformationJson: '{"client_id":"abc123"}',
397− codeVerifier: "verifier",
398− authorizationUrl: "https://github.com/login/oauth/authorize",
399− }),
400− errorText: null,
401− completedAt: null,
402− createdAt: now,
403− updatedAt: now,
404− });
405−
406− yield* runCodeMigrations(persistence.rows);
407−
408− const session =
409− yield* persistence.rows.sourceAuthSessions.getById(sessionId);
410− expect(Option.isSome(session)).toBe(true);
411− const repairedSession = session.pipe(Option.getOrNull);
412− expect(repairedSession).not.toBeNull();
413− const repaired = Schema.decodeUnknownSync(
414− McpSourceAuthSessionDataJsonSchema,
415− )(repairedSession!.sessionDataJson);
416− expect(repaired.resourceMetadata).toEqual({
417− issuer: "https://api.github.com",
418− });
419− expect(repaired.authorizationServerMetadata).toEqual({
420− token_endpoint: "https://github.com/login/oauth/access_token",
421− });
422− expect(repaired.clientInformation).toEqual({
423− client_id: "abc123",
424− });
425− }),
426− );
427−
428− it("upgrades legacy source binding columns into adapter-owned binding config", async () => {
429− const localDataDir = mkdtempSync(path.join(tmpdir(), "executor-cp-db-"));
430− const legacyMigrationsDir = copyMigrationsBefore(
431− "20260311010000_source_binding_configs",
432− );
433− const workspaceId = WorkspaceIdSchema.make("ws_legacy_source_bindings");
434− const accountId = AccountIdSchema.make("acc_legacy_source_bindings");
435− const now = Date.now();
436− let legacyPersistence: SqlControlPlanePersistence | null = null;
437− let upgradedPersistence: SqlControlPlanePersistence | null = null;
438−
439− try {
440− legacyPersistence = await Effect.runPromise(
441− createSqlControlPlanePersistence({
442− localDataDir,
443− migrationsFolder: legacyMigrationsDir,
444− runCodeMigrations: false,
445− }),
446− );
447−
448− const seedRecipe = async (input: {
449− sourceId: string;
450− kind: "openapi" | "graphql" | "mcp";
451− adapterKey: string;
452− providerKey: string;
453− name: string;
454− endpoint: string;
455− sourceConfigJson: string;
456− }) => {
457− const recipeId = SourceRecipeIdSchema.make(`src_recipe_${input.sourceId}`);
458− const recipeRevisionId = SourceRecipeRevisionIdSchema.make(
459− `src_recipe_rev_${input.sourceId}`,
460− );
461−
462− await legacyPersistence!.db.execute(sql`
463− INSERT INTO "source_recipes" (
464− "id",
465− "kind",
466− "importer_kind",
467− "provider_key",
468− "name",
469− "summary",
470− "visibility",
471− "latest_revision_id",
472− "created_at",
473− "updated_at"
474− ) VALUES (
475− ${recipeId},
476− ${input.kind === "mcp" ? "mcp" : "http_api"},
477− ${input.adapterKey},
478− ${input.providerKey},
479− ${input.name},
480− NULL,
481− 'workspace',
482− ${recipeRevisionId},
483− ${now},
484− ${now}
485− )
486− `);
487− await legacyPersistence!.db.execute(sql`
488− INSERT INTO "source_recipe_revisions" (
489− "id",
490− "recipe_id",
491− "revision_number",
492− "source_config_json",
493− "manifest_json",
494− "manifest_hash",
495− "created_at",
496− "updated_at"
497− ) VALUES (
498− ${recipeRevisionId},
499− ${recipeId},
500− 1,
501− ${input.sourceConfigJson},
502− NULL,
503− NULL,
504− ${now},
505− ${now}
506− )
507− `);
508−
509− return { recipeId, recipeRevisionId };
510− };
511−
512− const openapi = await seedRecipe({
513− sourceId: "src_legacy_openapi",
514− kind: "openapi",
515− adapterKey: "openapi",
516− providerKey: "generic_http",
517− name: "Legacy OpenAPI",
518− endpoint: "https://api.example.com",
519− sourceConfigJson: JSON.stringify({
520− kind: "openapi",
521− endpoint: "https://api.example.com",
522− specUrl: "https://api.example.com/openapi.json",
523− defaultHeaders: { accept: "application/json" },
524− }),
525− });
526− const graphql = await seedRecipe({
527− sourceId: "src_legacy_graphql",
528− kind: "graphql",
529− adapterKey: "graphql_introspection",
530− providerKey: "generic_graphql",
531− name: "Legacy GraphQL",
532− endpoint: "https://api.example.com/graphql",
533− sourceConfigJson: JSON.stringify({
534− kind: "graphql",
535− endpoint: "https://api.example.com/graphql",
536− defaultHeaders: { accept: "application/json" },
537− }),
538− });
539− const mcp = await seedRecipe({
540− sourceId: "src_legacy_mcp",
541− kind: "mcp",
542− adapterKey: "mcp_manifest",
543− providerKey: "generic_mcp",
544− name: "Legacy MCP",
545− endpoint: "https://api.example.com/mcp",
546− sourceConfigJson: JSON.stringify({
547− kind: "mcp",
548− endpoint: "https://api.example.com/mcp",
549− transport: "streamable-http",
550− queryParams: { tenant: "acme" },
551− headers: { "x-tenant": "acme" },
552− }),
553− });
554−
555− await legacyPersistence.db.execute(sql`
556− INSERT INTO "sources" (
557− "workspace_id",
558− "source_id",
559− "recipe_id",
560− "recipe_revision_id",
561− "name",
562− "kind",
563− "endpoint",
564− "status",
565− "enabled",
566− "namespace",
567− "binding_config_json",
568− "transport",
569− "query_params_json",
570− "headers_json",
571− "spec_url",
572− "default_headers_json",
573− "source_hash",
574− "source_document_text",
575− "last_error",
576− "created_at",
577− "updated_at"
578− ) VALUES
579− (
580− ${workspaceId},
581− ${SourceIdSchema.make("src_legacy_openapi")},
582− ${openapi.recipeId},
583− ${openapi.recipeRevisionId},
584− 'Legacy OpenAPI',
585− 'openapi',
586− 'https://api.example.com',
587− 'connected',
588− true,
589− 'legacy.openapi',
590− NULL,
591− NULL,
592− NULL,
593− NULL,
594− 'https://api.example.com/openapi.json',
595− '{"accept":"application/json"}',
596− NULL,
597− NULL,
598− NULL,
599− ${now},
600− ${now}
601− ),
602− (
603− ${workspaceId},
604− ${SourceIdSchema.make("src_legacy_graphql")},
605− ${graphql.recipeId},
606− ${graphql.recipeRevisionId},
607− 'Legacy GraphQL',
608− 'graphql',
609− 'https://api.example.com/graphql',
610− 'connected',
611− true,
612− 'legacy.graphql',
613− NULL,
614− NULL,
615− NULL,
616− NULL,
617− NULL,
618− '{"accept":"application/json"}',
619− NULL,
620− NULL,
621− NULL,
622− ${now},
623− ${now}
624− ),
625− (
626− ${workspaceId},
627− ${SourceIdSchema.make("src_legacy_mcp")},
628− ${mcp.recipeId},
629− ${mcp.recipeRevisionId},
630− 'Legacy MCP',
631− 'mcp',
632− 'https://api.example.com/mcp',
633− 'connected',
634− true,
635− 'legacy.mcp',
636− NULL,
637− 'streamable-http',
638− '{"tenant":"acme"}',
639− '{"x-tenant":"acme"}',
640− NULL,
641− NULL,
642− NULL,
643− NULL,
644− NULL,
645− ${now},
646− ${now}
647− );
648− `);
649−
650− await legacyPersistence.close();
651− legacyPersistence = null;
652−
653− upgradedPersistence = await Effect.runPromise(
654− createSqlControlPlanePersistence({
655− localDataDir,
656− }),
657− );
658−
659− const openApiSource = await Effect.runPromise(
660− upgradedPersistence.rows.sources.getByWorkspaceAndId(
661− workspaceId,
662− SourceIdSchema.make("src_legacy_openapi"),
663− ),
664− );
665− expect(
666− JSON.parse(Option.getOrThrow(openApiSource).bindingConfigJson),
667− ).toEqual({
668− adapterKey: "openapi",
669− version: 1,
670− payload: {
671− specUrl: "https://api.example.com/openapi.json",
672− defaultHeaders: {
673− accept: "application/json",
674− },
675− },
676− });
677−
678− const graphqlSource = await Effect.runPromise(
679− upgradedPersistence.rows.sources.getByWorkspaceAndId(
680− workspaceId,
681− SourceIdSchema.make("src_legacy_graphql"),
682− ),
683− );
684− expect(
685− JSON.parse(Option.getOrThrow(graphqlSource).bindingConfigJson),
686− ).toEqual({
687− adapterKey: "graphql",
688− version: 1,
689− payload: {
690− defaultHeaders: {
691− accept: "application/json",
692− },
693− },
694− });
695−
696− const graphqlRecipe = await Effect.runPromise(
697− upgradedPersistence.rows.sourceRecipes.getById(graphql.recipeId),
698− );
699− expect(Option.getOrNull(graphqlRecipe)?.adapterKey).toBe("graphql");
700−
701− const mcpSource = await Effect.runPromise(
702− upgradedPersistence.rows.sources.getByWorkspaceAndId(
703− workspaceId,
704− SourceIdSchema.make("src_legacy_mcp"),
705− ),
706− );
707− expect(
708− JSON.parse(Option.getOrThrow(mcpSource).bindingConfigJson),
709− ).toEqual({
710− adapterKey: "mcp",
711− version: 1,
712− payload: {
713− transport: "streamable-http",
714− queryParams: {
715− tenant: "acme",
716− },
717− headers: {
718− "x-tenant": "acme",
719− },
720− },
721− });
722−
723− const mcpRecipe = await Effect.runPromise(
724− upgradedPersistence.rows.sourceRecipes.getById(mcp.recipeId),
725− );
726− expect(Option.getOrNull(mcpRecipe)?.adapterKey).toBe("mcp");
727− } finally {
728− await legacyPersistence?.close().catch(() => undefined);
729− await upgradedPersistence?.close().catch(() => undefined);
730− rmSync(localDataDir, { recursive: true, force: true });
731− rmSync(legacyMigrationsDir, { recursive: true, force: true });
732− }
733− });
734−
735− it("upgrades auth leases into template-based placement storage", async () => {
736− const localDataDir = mkdtempSync(path.join(tmpdir(), "executor-cp-db-"));
737− const legacyMigrationsDir = copyMigrationsBefore(
738− "20260312143000_auth_lease_template_placements",
739− );
740− const workspaceId = WorkspaceIdSchema.make("ws_legacy_auth_lease");
741− const accountId = AccountIdSchema.make("acc_legacy_auth_lease");
742− const sourceId = SourceIdSchema.make("src_legacy_auth_lease");
743− const recipeId = SourceRecipeIdSchema.make("src_recipe_legacy_auth_lease");
744− const recipeRevisionId = SourceRecipeRevisionIdSchema.make(
745− "src_recipe_rev_legacy_auth_lease",
746− );
747− const authArtifactId = "auth_art_legacy_auth_lease";
748− const now = Date.now();
749− let legacyPersistence: SqlControlPlanePersistence | null = null;
750− let upgradedPersistence: SqlControlPlanePersistence | null = null;
751−
752− try {
753− legacyPersistence = await Effect.runPromise(
754− createSqlControlPlanePersistence({
755− localDataDir,
756− migrationsFolder: legacyMigrationsDir,
757− runCodeMigrations: false,
758− }),
759− );
760−
761− await Effect.runPromise(legacyPersistence.rows.sourceRecipes.upsert({
762− id: recipeId,
763− kind: "http_api",
764− adapterKey: "openapi",
765− providerKey: "generic_http",
766− name: "Legacy OpenAPI",
767− summary: null,
768− visibility: "workspace",
769− latestRevisionId: recipeRevisionId,
770− createdAt: now,
771− updatedAt: now,
772− }));
773− await Effect.runPromise(legacyPersistence.rows.sourceRecipeRevisions.upsert(baseRevisionRecord({
774− id: recipeRevisionId,
775− recipeId,
776− revisionNumber: 1,
777− sourceConfigJson: JSON.stringify({
778− kind: "openapi",
779− endpoint: "https://api.example.com",
780− specUrl: "https://api.example.com/openapi.json",
781− }),
782− createdAt: now,
783− updatedAt: now,
784− })));
785− await legacyPersistence.db.execute(sql`
786− INSERT INTO "sources" (
787− "source_id",
788− "workspace_id",
789− "recipe_id",
790− "recipe_revision_id",
791− "name",
792− "kind",
793− "endpoint",
794− "status",
795− "enabled",
796− "namespace",
797− "import_auth_policy",
798− "binding_config_json",
799− "source_hash",
800− "last_error",
801− "created_at",
802− "updated_at"
803− ) VALUES (
804− ${sourceId},
805− ${workspaceId},
806− ${recipeId},
807− ${recipeRevisionId},
808− ${"Legacy OpenAPI"},
809− ${"openapi"},
810− ${"https://api.example.com"},
811− ${"connected"},
812− ${true},
813− ${"legacy.openapi"},
814− ${"reuse_runtime"},
815− ${openApiBindingConfigJson("https://api.example.com/openapi.json")},
816− NULL,
817− NULL,
818− ${now},
819− ${now}
820− )
821− `);
822− await Effect.runPromise(legacyPersistence.rows.authArtifacts.upsert({
823− id: authArtifactId as any,
824− workspaceId,
825− sourceId,
826− actorAccountId: accountId,
827− slot: "runtime",
828− artifactKind: "static_bearer",
829− configJson: JSON.stringify({
830− headerName: "Authorization",
831− prefix: "Bearer ",
832− token: {
833− providerId: "postgres",
834− handle: "sec_legacy_auth_lease_token",
835− },
836− }),
837− grantSetJson: null,
838− createdAt: now,
839− updatedAt: now,
840− }));
841−
842− await legacyPersistence.db.execute(sql`
843− INSERT INTO "workspace_source_auth_leases" (
844− "id",
845− "auth_artifact_id",
846− "workspace_id",
847− "source_id",
848− "actor_account_id",
849− "slot",
850− "placements_json",
851− "expires_at",
852− "refresh_after",
853− "created_at",
854− "updated_at"
855− ) VALUES (
856− 'auth_lease_legacy_auth_lease',
857− ${authArtifactId},
858− ${workspaceId},
859− ${sourceId},
860− ${accountId},
861− 'runtime',
862− '[{"location":"header","name":"Authorization","value":"Bearer abc"},{"location":"query","name":"api_key","value":"q123"},{"location":"cookie","name":"sid","value":"cookie123"},{"location":"body","path":"auth.token","value":"body123"}]',
863− NULL,
864− NULL,
865− ${now},
866− ${now}
867− );
868− `);
869−
870− await legacyPersistence.close();
871− legacyPersistence = null;
872−
873− upgradedPersistence = await Effect.runPromise(
874− createSqlControlPlanePersistence({
875− localDataDir,
876− }),
877− );
878−
879− const leaseOption = await Effect.runPromise(
880− upgradedPersistence.rows.authLeases.getByAuthArtifactId(authArtifactId as any),
881− );
882− expect(Option.isSome(leaseOption)).toBe(true);
883− const lease = Option.getOrNull(leaseOption);
884− expect(lease).not.toBeNull();
885− expect(lease?.placementsTemplateJson).not.toBeNull();
886− expect(decodeAuthLeasePlacementTemplates(lease!)).toEqual([
887− {
888− location: "header",
889− name: "Authorization",
890− parts: [{ kind: "literal", value: "Bearer abc" }],
891− },
892− {
893− location: "query",
894− name: "api_key",
895− parts: [{ kind: "literal", value: "q123" }],
896− },
897− {
898− location: "cookie",
899− name: "sid",
900− parts: [{ kind: "literal", value: "cookie123" }],
901− },
902− {
903− location: "body",
904− path: "auth.token",
905− parts: [{ kind: "literal", value: "body123" }],
906− },
907− ]);
908− } finally {
909− await legacyPersistence?.close().catch(() => undefined);
910− await upgradedPersistence?.close().catch(() => undefined);
911− rmSync(localDataDir, { recursive: true, force: true });
912− rmSync(legacyMigrationsDir, { recursive: true, force: true });
913− }
914− });
915−});
packages/control-plane/src/persistence/repos/accounts-repo.ts+0−71
@@ -1,71 +0,0 @@
1−import { type Account, AccountSchema } from "#schema";
2−import * as Option from "effect/Option";
3−import { Schema } from "effect";
4−import { and, eq } from "drizzle-orm";
5−
6−import type { DrizzleClient } from "../client";
7−import type { DrizzleTables } from "../schema";
8−import { firstOption } from "./shared";
9−
10−const decodeAccount = Schema.decodeUnknownSync(AccountSchema);
11−
12−export const createAccountsRepo = (
13− client: DrizzleClient,
14− tables: DrizzleTables,
15−) => ({
16− getById: (accountId: Account["id"]) =>
17− client.use("rows.accounts.get_by_id", async (db) => {
18− const rows = await db
19− .select()
20− .from(tables.accountsTable)
21− .where(eq(tables.accountsTable.id, accountId))
22− .limit(1);
23−
24− const row = firstOption(rows);
25− return Option.isSome(row)
26− ? Option.some(decodeAccount(row.value))
27− : Option.none<Account>();
28− }),
29−
30− getByProviderAndSubject: (
31− provider: Account["provider"],
32− subject: Account["subject"],
33− ) =>
34− client.use("rows.accounts.get_by_provider_and_subject", async (db) => {
35− const rows = await db
36− .select()
37− .from(tables.accountsTable)
38− .where(
39− and(
40− eq(tables.accountsTable.provider, provider),
41− eq(tables.accountsTable.subject, subject),
42− ),
43− )
44− .limit(1);
45−
46− const row = firstOption(rows);
47− return Option.isSome(row)
48− ? Option.some(decodeAccount(row.value))
49− : Option.none<Account>();
50− }),
51−
52− insert: (account: Account) =>
53− client.use("rows.accounts.insert", async (db) => {
54− await db.insert(tables.accountsTable).values(account);
55− }),
56−
57− upsert: (account: Account) =>
58− client.use("rows.accounts.upsert", async (db) => {
59− await db
60− .insert(tables.accountsTable)
61− .values(account)
62− .onConflictDoUpdate({
63− target: [tables.accountsTable.provider, tables.accountsTable.subject],
64− set: {
65− email: account.email,
66− displayName: account.displayName,
67− updatedAt: account.updatedAt,
68− },
69− });
70− }),
71−});
packages/control-plane/src/persistence/repos/auth-artifacts-repo.ts+0−226
@@ -1,226 +0,0 @@
1−import {
2− AuthArtifactSchema,
3− type AuthArtifact,
4−} from "#schema";
5−import * as Option from "effect/Option";
6−import { Schema } from "effect";
7−import { and, asc, eq, inArray, isNull, or } from "drizzle-orm";
8−
9−import type { DrizzleClient } from "../client";
10−import type { DrizzleTables } from "../schema";
11−import { firstOption } from "./shared";
12−
13−const decodeAuthArtifact = Schema.decodeUnknownSync(AuthArtifactSchema);
14−
15−const authArtifactUpdateSet = (artifact: AuthArtifact) => {
16− const {
17− id: _id,
18− workspaceId: _workspaceId,
19− sourceId: _sourceId,
20− actorAccountId: _actorAccountId,
21− createdAt: _createdAt,
22− ...patch
23− } = artifact;
24− return patch;
25−};
26−
27−export const createAuthArtifactsRepo = (
28− client: DrizzleClient,
29− tables: DrizzleTables,
30−) => ({
31− listByWorkspaceId: (workspaceId: AuthArtifact["workspaceId"]) =>
32− client.use("rows.auth_artifacts.list_by_workspace", async (db) => {
33− const rows = await db
34− .select()
35− .from(tables.authArtifactsTable)
36− .where(eq(tables.authArtifactsTable.workspaceId, workspaceId))
37− .orderBy(
38− asc(tables.authArtifactsTable.updatedAt),
39− asc(tables.authArtifactsTable.id),
40− );
41−
42− return rows.map((row) => decodeAuthArtifact(row));
43− }),
44−
45− listByWorkspaceAndSourceId: (input: {
46− workspaceId: AuthArtifact["workspaceId"];
47− sourceId: AuthArtifact["sourceId"];
48− }) =>
49− client.use("rows.auth_artifacts.list_by_workspace_source", async (db) => {
50− const rows = await db
51− .select()
52− .from(tables.authArtifactsTable)
53− .where(
54− and(
55− eq(tables.authArtifactsTable.workspaceId, input.workspaceId),
56− eq(tables.authArtifactsTable.sourceId, input.sourceId),
57− ),
58− )
59− .orderBy(
60− asc(tables.authArtifactsTable.updatedAt),
61− asc(tables.authArtifactsTable.id),
62− );
63−
64− return rows.map((row) => decodeAuthArtifact(row));
65− }),
66−
67− listByWorkspaceSourceAndActor: (input: {
68− workspaceId: AuthArtifact["workspaceId"];
69− sourceId: AuthArtifact["sourceId"];
70− actorAccountId: AuthArtifact["actorAccountId"];
71− }) =>
72− client.use("rows.auth_artifacts.list_by_workspace_source_actor", async (db) => {
73− const rows = await db
74− .select()
75− .from(tables.authArtifactsTable)
76− .where(
77− and(
78− eq(tables.authArtifactsTable.workspaceId, input.workspaceId),
79− eq(tables.authArtifactsTable.sourceId, input.sourceId),
80− or(
81− input.actorAccountId === null
82− ? isNull(tables.authArtifactsTable.actorAccountId)
83− : eq(tables.authArtifactsTable.actorAccountId, input.actorAccountId),
84− input.actorAccountId === null
85− ? undefined
86− : isNull(tables.authArtifactsTable.actorAccountId),
87− ),
88− ),
89− )
90− .orderBy(
91− asc(tables.authArtifactsTable.slot),
92− asc(tables.authArtifactsTable.actorAccountId),
93− asc(tables.authArtifactsTable.updatedAt),
94− asc(tables.authArtifactsTable.id),
95− );
96−
97− return rows.map((row) => decodeAuthArtifact(row));
98− }),
99−
100− getByWorkspaceSourceAndActor: (input: {
101− workspaceId: AuthArtifact["workspaceId"];
102− sourceId: AuthArtifact["sourceId"];
103− actorAccountId: AuthArtifact["actorAccountId"];
104− slot: AuthArtifact["slot"];
105− }) =>
106− client.use("rows.auth_artifacts.get_by_workspace_source_actor", async (db) => {
107− const rows = await db
108− .select()
109− .from(tables.authArtifactsTable)
110− .where(
111− and(
112− eq(tables.authArtifactsTable.workspaceId, input.workspaceId),
113− eq(tables.authArtifactsTable.sourceId, input.sourceId),
114− eq(tables.authArtifactsTable.slot, input.slot),
115− input.actorAccountId === null
116− ? isNull(tables.authArtifactsTable.actorAccountId)
117− : eq(tables.authArtifactsTable.actorAccountId, input.actorAccountId),
118− ),
119− )
120− .limit(1);
121−
122− const row = firstOption(rows);
123− return Option.isSome(row)
124− ? Option.some(decodeAuthArtifact(row.value))
125− : Option.none<AuthArtifact>();
126− }),
127−
128− upsert: (artifact: AuthArtifact) =>
129− client.use("rows.auth_artifacts.upsert", async (db) => {
130− if (artifact.actorAccountId === null) {
131− const existingRows = await db
132− .select({
133− id: tables.authArtifactsTable.id,
134− })
135− .from(tables.authArtifactsTable)
136− .where(
137− and(
138− eq(tables.authArtifactsTable.workspaceId, artifact.workspaceId),
139− eq(tables.authArtifactsTable.sourceId, artifact.sourceId),
140− eq(tables.authArtifactsTable.slot, artifact.slot),
141− isNull(tables.authArtifactsTable.actorAccountId),
142− ),
143− )
144− .orderBy(
145− asc(tables.authArtifactsTable.updatedAt),
146− asc(tables.authArtifactsTable.id),
147− );
148−
149− const existing = firstOption(existingRows);
150− if (Option.isSome(existing)) {
151− await db
152− .update(tables.authArtifactsTable)
153− .set(authArtifactUpdateSet(artifact))
154− .where(eq(tables.authArtifactsTable.id, existing.value.id));
155−
156− const duplicateIds = existingRows.slice(1).map((row) => row.id);
157− if (duplicateIds.length > 0) {
158− await db
159− .delete(tables.authArtifactsTable)
160− .where(inArray(tables.authArtifactsTable.id, duplicateIds));
161− }
162−
163− return;
164− }
165− }
166−
167− await db
168− .insert(tables.authArtifactsTable)
169− .values(artifact)
170− .onConflictDoUpdate({
171− target: [
172− tables.authArtifactsTable.workspaceId,
173− tables.authArtifactsTable.sourceId,
174− tables.authArtifactsTable.actorAccountId,
175− tables.authArtifactsTable.slot,
176− ],
177− set: {
178− ...authArtifactUpdateSet(artifact),
179− },
180− });
181− }),
182−
183− removeByWorkspaceSourceAndActor: (input: {
184− workspaceId: AuthArtifact["workspaceId"];
185− sourceId: AuthArtifact["sourceId"];
186− actorAccountId: AuthArtifact["actorAccountId"];
187− slot?: AuthArtifact["slot"];
188− }) =>
189− client.use("rows.auth_artifacts.remove_by_workspace_source_actor", async (db) => {
190− const deleted = await db
191− .delete(tables.authArtifactsTable)
192− .where(
193− and(
194− eq(tables.authArtifactsTable.workspaceId, input.workspaceId),
195− eq(tables.authArtifactsTable.sourceId, input.sourceId),
196− input.slot === undefined
197− ? undefined
198− : eq(tables.authArtifactsTable.slot, input.slot),
199− input.actorAccountId === null
200− ? isNull(tables.authArtifactsTable.actorAccountId)
201− : eq(tables.authArtifactsTable.actorAccountId, input.actorAccountId),
202− ),
203− )
204− .returning();
205−
206− return deleted.length > 0;
207− }),
208−
209− removeByWorkspaceAndSourceId: (input: {
210− workspaceId: AuthArtifact["workspaceId"];
211− sourceId: AuthArtifact["sourceId"];
212− }) =>
213− client.use("rows.auth_artifacts.remove_by_workspace_source", async (db) => {
214− const deleted = await db
215− .delete(tables.authArtifactsTable)
216− .where(
217− and(
218− eq(tables.authArtifactsTable.workspaceId, input.workspaceId),
219− eq(tables.authArtifactsTable.sourceId, input.sourceId),
220− ),
221− )
222− .returning();
223−
224− return deleted.length;
225− }),
226−});
packages/control-plane/src/persistence/repos/auth-leases-repo.ts+0−145
@@ -1,145 +0,0 @@
1−import {
2− AuthLeaseSchema,
3− type AuthLease,
4−} from "#schema";
5−import * as Option from "effect/Option";
6−import { Schema } from "effect";
7−import { and, asc, eq, isNull } from "drizzle-orm";
8−
9−import type { DrizzleClient } from "../client";
10−import type { DrizzleTables } from "../schema";
11−import { firstOption, withoutCreatedAt } from "./shared";
12−
13−const decodeAuthLease = Schema.decodeUnknownSync(AuthLeaseSchema);
14−
15−export const createAuthLeasesRepo = (
16− client: DrizzleClient,
17− tables: DrizzleTables,
18−) => ({
19− getByAuthArtifactId: (authArtifactId: AuthLease["authArtifactId"]) =>
20− client.use("rows.auth_leases.get_by_auth_artifact_id", async (db) => {
21− const rows = await db
22− .select()
23− .from(tables.authLeasesTable)
24− .where(eq(tables.authLeasesTable.authArtifactId, authArtifactId))
25− .limit(1);
26−
27− const row = firstOption(rows);
28− return Option.isSome(row)
29− ? Option.some(decodeAuthLease(row.value))
30− : Option.none<AuthLease>();
31− }),
32−
33− listByWorkspaceId: (workspaceId: AuthLease["workspaceId"]) =>
34− client.use("rows.auth_leases.list_by_workspace", async (db) => {
35− const rows = await db
36− .select()
37− .from(tables.authLeasesTable)
38− .where(eq(tables.authLeasesTable.workspaceId, workspaceId))
39− .orderBy(
40− asc(tables.authLeasesTable.updatedAt),
41− asc(tables.authLeasesTable.id),
42− );
43−
44− return rows.map((row) => decodeAuthLease(row));
45− }),
46−
47− getByWorkspaceSourceAndActor: (input: {
48− workspaceId: AuthLease["workspaceId"];
49− sourceId: AuthLease["sourceId"];
50− actorAccountId: AuthLease["actorAccountId"];
51− slot: AuthLease["slot"];
52− }) =>
53− client.use("rows.auth_leases.get_by_workspace_source_actor", async (db) => {
54− const rows = await db
55− .select()
56− .from(tables.authLeasesTable)
57− .where(
58− and(
59− eq(tables.authLeasesTable.workspaceId, input.workspaceId),
60− eq(tables.authLeasesTable.sourceId, input.sourceId),
61− eq(tables.authLeasesTable.slot, input.slot),
62− input.actorAccountId === null
63− ? isNull(tables.authLeasesTable.actorAccountId)
64− : eq(tables.authLeasesTable.actorAccountId, input.actorAccountId),
65− ),
66− )
67− .orderBy(
68− asc(tables.authLeasesTable.updatedAt),
69− asc(tables.authLeasesTable.id),
70− )
71− .limit(1);
72−
73− const row = firstOption(rows);
74− return Option.isSome(row)
75− ? Option.some(decodeAuthLease(row.value))
76− : Option.none<AuthLease>();
77− }),
78−
79− upsert: (lease: AuthLease) =>
80− client.use("rows.auth_leases.upsert", async (db) => {
81− await db
82− .insert(tables.authLeasesTable)
83− .values(lease)
84− .onConflictDoUpdate({
85− target: [tables.authLeasesTable.authArtifactId],
86− set: {
87− ...withoutCreatedAt(lease),
88− },
89− });
90− }),
91−
92− removeByAuthArtifactId: (authArtifactId: AuthLease["authArtifactId"]) =>
93− client.use("rows.auth_leases.remove_by_auth_artifact_id", async (db) => {
94− const deleted = await db
95− .delete(tables.authLeasesTable)
96− .where(eq(tables.authLeasesTable.authArtifactId, authArtifactId))
97− .returning();
98−
99− return deleted.length > 0;
100− }),
101−
102− removeByWorkspaceSourceAndActor: (input: {
103− workspaceId: AuthLease["workspaceId"];
104− sourceId: AuthLease["sourceId"];
105− actorAccountId: AuthLease["actorAccountId"];
106− slot?: AuthLease["slot"];
107− }) =>
108− client.use("rows.auth_leases.remove_by_workspace_source_actor", async (db) => {
109− const deleted = await db
110− .delete(tables.authLeasesTable)
111− .where(
112− and(
113− eq(tables.authLeasesTable.workspaceId, input.workspaceId),
114− eq(tables.authLeasesTable.sourceId, input.sourceId),
115− input.slot === undefined
116− ? undefined
117− : eq(tables.authLeasesTable.slot, input.slot),
118− input.actorAccountId === null
119− ? isNull(tables.authLeasesTable.actorAccountId)
120− : eq(tables.authLeasesTable.actorAccountId, input.actorAccountId),
121− ),
122− )
123− .returning();
124−
125− return deleted.length > 0;
126− }),
127−
128− removeByWorkspaceAndSourceId: (input: {
129− workspaceId: AuthLease["workspaceId"];
130− sourceId: AuthLease["sourceId"];
131− }) =>
132− client.use("rows.auth_leases.remove_by_workspace_source", async (db) => {
133− const deleted = await db
134− .delete(tables.authLeasesTable)
135− .where(
136− and(
137− eq(tables.authLeasesTable.workspaceId, input.workspaceId),
138− eq(tables.authLeasesTable.sourceId, input.sourceId),
139− ),
140− )
141− .returning();
142−
143− return deleted.length;
144− }),
145−});
packages/control-plane/src/persistence/repos/code-migrations-repo.ts+0−41
@@ -1,41 +0,0 @@
1−import type {
2− StoredCodeMigrationRecord,
3−} from "#schema";
4−import { StoredCodeMigrationRecordSchema } from "#schema";
5−import { Schema } from "effect";
6−import { asc } from "drizzle-orm";
7−
8−import type { DrizzleClient } from "../client";
9−import type { DrizzleTables } from "../schema";
10−
11−const decodeStoredCodeMigrationRecord = Schema.decodeUnknownSync(
12− StoredCodeMigrationRecordSchema,
13−);
14−
15−export const createCodeMigrationsRepo = (
16− client: DrizzleClient,
17− tables: DrizzleTables,
18−) => ({
19− listAll: () =>
20− client.use("rows.code_migrations.list_all", async (db) => {
21− const rows = await db
22− .select()
23− .from(tables.codeMigrationsTable)
24− .orderBy(asc(tables.codeMigrationsTable.appliedAt));
25−
26− return rows.map((row) => decodeStoredCodeMigrationRecord(row));
27− }),
28−
29− upsert: (record: StoredCodeMigrationRecord) =>
30− client.use("rows.code_migrations.upsert", async (db) => {
31− await db
32− .insert(tables.codeMigrationsTable)
33− .values(record)
34− .onConflictDoNothing();
35− }),
36−
37− clearAll: () =>
38− client.use("rows.code_migrations.clear_all", async (db) => {
39− await db.delete(tables.codeMigrationsTable);
40− }),
41−});
packages/control-plane/src/persistence/repos/execution-interactions-repo.ts+0−96
@@ -1,96 +0,0 @@
1−import {
2− type ExecutionInteraction,
3− ExecutionInteractionSchema,
4−} from "#schema";
5−import * as Option from "effect/Option";
6−import { Schema } from "effect";
7−import { and, desc, eq } from "drizzle-orm";
8−
9−import type { DrizzleClient } from "../client";
10−import type { DrizzleTables } from "../schema";
11−import { firstOption } from "./shared";
12−
13−const decodeExecutionInteraction = Schema.decodeUnknownSync(
14− ExecutionInteractionSchema,
15−);
16−
17−export const createExecutionInteractionsRepo = (
18− client: DrizzleClient,
19− tables: DrizzleTables,
20−) => ({
21− getById: (interactionId: ExecutionInteraction["id"]) =>
22− client.use("rows.execution_interactions.get_by_id", async (db) => {
23− const rows = await db
24− .select()
25− .from(tables.executionInteractionsTable)
26− .where(eq(tables.executionInteractionsTable.id, interactionId))
27− .limit(1);
28−
29− const row = firstOption(rows);
30− return Option.isSome(row)
31− ? Option.some(decodeExecutionInteraction(row.value))
32− : Option.none<ExecutionInteraction>();
33− }),
34−
35− listByExecutionId: (executionId: ExecutionInteraction["executionId"]) =>
36− client.use("rows.execution_interactions.list_by_execution_id", async (db) => {
37− const rows = await db
38− .select()
39− .from(tables.executionInteractionsTable)
40− .where(eq(tables.executionInteractionsTable.executionId, executionId))
41− .orderBy(
42− desc(tables.executionInteractionsTable.updatedAt),
43− desc(tables.executionInteractionsTable.id),
44− );
45−
46− return rows.map((row) => decodeExecutionInteraction(row));
47− }),
48−
49− getPendingByExecutionId: (executionId: ExecutionInteraction["executionId"]) =>
50− client.use(
51− "rows.execution_interactions.get_pending_by_execution_id",
52− async (db) => {
53− const rows = await db
54− .select()
55− .from(tables.executionInteractionsTable)
56− .where(
57− and(
58− eq(tables.executionInteractionsTable.executionId, executionId),
59− eq(tables.executionInteractionsTable.status, "pending"),
60− ),
61− )
62− .orderBy(
63− desc(tables.executionInteractionsTable.updatedAt),
64− desc(tables.executionInteractionsTable.id),
65− )
66− .limit(1);
67−
68− const row = firstOption(rows);
69− return Option.isSome(row)
70− ? Option.some(decodeExecutionInteraction(row.value))
71− : Option.none<ExecutionInteraction>();
72− },
73− ),
74−
75− insert: (interaction: ExecutionInteraction) =>
76− client.use("rows.execution_interactions.insert", async (db) => {
77− await db.insert(tables.executionInteractionsTable).values(interaction);
78− }),
79−
80− update: (
81− interactionId: ExecutionInteraction["id"],
82− patch: Partial<Omit<ExecutionInteraction, "id" | "executionId" | "createdAt">>,
83− ) =>
84− client.use("rows.execution_interactions.update", async (db) => {
85− const rows = await db
86− .update(tables.executionInteractionsTable)
87− .set(patch)
88− .where(eq(tables.executionInteractionsTable.id, interactionId))
89− .returning();
90−
91− const row = firstOption(rows);
92− return Option.isSome(row)
93− ? Option.some(decodeExecutionInteraction(row.value))
94− : Option.none<ExecutionInteraction>();
95− }),
96−});
packages/control-plane/src/persistence/repos/execution-steps-repo.ts+0−89
@@ -1,89 +0,0 @@
1−import {
2− type ExecutionStep,
3− ExecutionStepSchema,
4−} from "#schema";
5−import * as Option from "effect/Option";
6−import { Schema } from "effect";
7−import { and, desc, eq } from "drizzle-orm";
8−
9−import type { DrizzleClient } from "../client";
10−import type { DrizzleTables } from "../schema";
11−import { firstOption } from "./shared";
12−
13−const decodeExecutionStep = Schema.decodeUnknownSync(ExecutionStepSchema);
14−
15−export const createExecutionStepsRepo = (
16− client: DrizzleClient,
17− tables: DrizzleTables,
18−) => ({
19− getByExecutionAndSequence: (
20− executionId: ExecutionStep["executionId"],
21− sequence: ExecutionStep["sequence"],
22− ) =>
23− client.use("rows.execution_steps.get_by_execution_and_sequence", async (db) => {
24− const rows = await db
25− .select()
26− .from(tables.executionStepsTable)
27− .where(
28− and(
29− eq(tables.executionStepsTable.executionId, executionId),
30− eq(tables.executionStepsTable.sequence, sequence),
31− ),
32− )
33− .limit(1);
34−
35− const row = firstOption(rows);
36− return Option.isSome(row)
37− ? Option.some(decodeExecutionStep(row.value))
38− : Option.none<ExecutionStep>();
39− }),
40−
41− listByExecutionId: (executionId: ExecutionStep["executionId"]) =>
42− client.use("rows.execution_steps.list_by_execution_id", async (db) => {
43− const rows = await db
44− .select()
45− .from(tables.executionStepsTable)
46− .where(eq(tables.executionStepsTable.executionId, executionId))
47− .orderBy(
48− tables.executionStepsTable.sequence,
49− desc(tables.executionStepsTable.updatedAt),
50− );
51−
52− return rows.map((row) => decodeExecutionStep(row));
53− }),
54−
55− insert: (step: ExecutionStep) =>
56− client.use("rows.execution_steps.insert", async (db) => {
57− await db.insert(tables.executionStepsTable).values(step);
58− }),
59−
60− deleteByExecutionId: (executionId: ExecutionStep["executionId"]) =>
61− client.use("rows.execution_steps.delete_by_execution_id", async (db) => {
62− await db
63− .delete(tables.executionStepsTable)
64− .where(eq(tables.executionStepsTable.executionId, executionId));
65− }),
66−
67− updateByExecutionAndSequence: (
68− executionId: ExecutionStep["executionId"],
69− sequence: ExecutionStep["sequence"],
70− patch: Partial<Omit<ExecutionStep, "id" | "executionId" | "sequence" | "createdAt">>,
71− ) =>
72− client.use("rows.execution_steps.update_by_execution_and_sequence", async (db) => {
73− const rows = await db
74− .update(tables.executionStepsTable)
75− .set(patch)
76− .where(
77− and(
78− eq(tables.executionStepsTable.executionId, executionId),
79− eq(tables.executionStepsTable.sequence, sequence),
80− ),
81− )
82− .returning();
83−
84− const row = firstOption(rows);
85− return Option.isSome(row)
86− ? Option.some(decodeExecutionStep(row.value))
87− : Option.none<ExecutionStep>();
88− }),
89−});
packages/control-plane/src/persistence/repos/executions-repo.ts+0−75
@@ -1,75 +0,0 @@
1−import { type Execution, ExecutionSchema } from "#schema";
2−import * as Option from "effect/Option";
3−import { Schema } from "effect";
4−import { and, eq } from "drizzle-orm";
5−
6−import type { DrizzleClient } from "../client";
7−import type { DrizzleTables } from "../schema";
8−import { firstOption } from "./shared";
9−
10−const decodeExecution = Schema.decodeUnknownSync(ExecutionSchema);
11−
12−export const createExecutionsRepo = (
13− client: DrizzleClient,
14− tables: DrizzleTables,
15−) => ({
16− getById: (executionId: Execution["id"]) =>
17− client.use("rows.executions.get_by_id", async (db) => {
18− const rows = await db
19− .select()
20− .from(tables.executionsTable)
21− .where(eq(tables.executionsTable.id, executionId))
22− .limit(1);
23−
24− const row = firstOption(rows);
25− return Option.isSome(row)
26− ? Option.some(decodeExecution(row.value))
27− : Option.none<Execution>();
28− }),
29−
30− getByWorkspaceAndId: (
31− workspaceId: Execution["workspaceId"],
32− executionId: Execution["id"],
33− ) =>
34− client.use("rows.executions.get_by_workspace_and_id", async (db) => {
35− const rows = await db
36− .select()
37− .from(tables.executionsTable)
38− .where(
39− and(
40− eq(tables.executionsTable.workspaceId, workspaceId),
41− eq(tables.executionsTable.id, executionId),
42− ),
43− )
44− .limit(1);
45−
46− const row = firstOption(rows);
47− return Option.isSome(row)
48− ? Option.some(decodeExecution(row.value))
49− : Option.none<Execution>();
50− }),
51−
52− insert: (execution: Execution) =>
53− client.use("rows.executions.insert", async (db) => {
54− await db.insert(tables.executionsTable).values(execution);
55− }),
56−
57− update: (
58− executionId: Execution["id"],
59− patch: Partial<
60− Omit<Execution, "id" | "workspaceId" | "createdByAccountId" | "createdAt">
61− >,
62− ) =>
63− client.use("rows.executions.update", async (db) => {
64− const rows = await db
65− .update(tables.executionsTable)
66− .set(patch)
67− .where(eq(tables.executionsTable.id, executionId))
68− .returning();
69−
70− const row = firstOption(rows);
71− return Option.isSome(row)
72− ? Option.some(decodeExecution(row.value))
73− : Option.none<Execution>();
74− }),
75−});
packages/control-plane/src/persistence/repos/index.ts+0−16
@@ -1,16 +0,0 @@
1−export { createAccountsRepo } from "./accounts-repo";
2−export { createSourcesRepo } from "./sources-repo";
3−export { createSourceRecipesRepo } from "./source-recipes-repo";
4−export { createSourceRecipeRevisionsRepo } from "./source-recipe-revisions-repo";
5−export { createSourceRecipeDocumentsRepo } from "./source-recipe-documents-repo";
6−export { createSourceRecipeSchemaBundlesRepo } from "./source-recipe-schema-bundles-repo";
7−export { createSourceRecipeOperationsRepo } from "./source-recipe-operations-repo";
8−export { createCodeMigrationsRepo } from "./code-migrations-repo";
9−export { createAuthArtifactsRepo } from "./auth-artifacts-repo";
10−export { createAuthLeasesRepo } from "./auth-leases-repo";
11−export { createSourceOauthClientsRepo } from "./source-oauth-clients-repo";
12−export { createSecretMaterialsRepo } from "./secret-materials-repo";
13−export { createSourceAuthSessionsRepo } from "./source-auth-sessions-repo";
14−export { createExecutionsRepo } from "./executions-repo";
15−export { createExecutionInteractionsRepo } from "./execution-interactions-repo";
16−export { createExecutionStepsRepo } from "./execution-steps-repo";
packages/control-plane/src/persistence/repos/secret-materials-repo.ts+0−144
@@ -1,144 +0,0 @@
1−import {
2− authArtifactSecretRefs,
3− type SecretMaterial,
4− SecretMaterialSchema,
5−} from "#schema";
6−import * as Option from "effect/Option";
7−import { Schema } from "effect";
8−import { eq, desc, and } from "drizzle-orm";
9−
10−import type { DrizzleClient } from "../client";
11−import type { DrizzleTables } from "../schema";
12−import { firstOption, withoutCreatedAt } from "./shared";
13−
14−const decodeSecretMaterial = Schema.decodeUnknownSync(SecretMaterialSchema);
15−
16−export const createSecretMaterialsRepo = (
17− client: DrizzleClient,
18− tables: DrizzleTables,
19−) => ({
20− getById: (id: SecretMaterial["id"]) =>
21− client.use("rows.secret_materials.get_by_id", async (db) => {
22− const rows = await db
23− .select()
24− .from(tables.secretMaterialsTable)
25− .where(eq(tables.secretMaterialsTable.id, id))
26− .limit(1);
27−
28− const row = firstOption(rows);
29− return Option.isSome(row)
30− ? Option.some(decodeSecretMaterial(row.value))
31− : Option.none<SecretMaterial>();
32− }),
33−
34− listAll: () =>
35− client.use("rows.secret_materials.list_all", async (db) => {
36− const rows = await db
37− .select({
38− id: tables.secretMaterialsTable.id,
39− name: tables.secretMaterialsTable.name,
40− purpose: tables.secretMaterialsTable.purpose,
41− createdAt: tables.secretMaterialsTable.createdAt,
42− updatedAt: tables.secretMaterialsTable.updatedAt,
43− })
44− .from(tables.secretMaterialsTable)
45− .orderBy(desc(tables.secretMaterialsTable.updatedAt));
46−
47− return rows;
48− }),
49−
50− upsert: (material: SecretMaterial) =>
51− client.use("rows.secret_materials.upsert", async (db) => {
52− await db
53− .insert(tables.secretMaterialsTable)
54− .values(material)
55− .onConflictDoUpdate({
56− target: [tables.secretMaterialsTable.id],
57− set: {
58− ...withoutCreatedAt(material),
59− },
60− });
61− }),
62−
63− updateById: (id: SecretMaterial["id"], update: { name?: string | null; value?: string }) =>
64− client.use("rows.secret_materials.update_by_id", async (db) => {
65− const set: Record<string, unknown> = { updatedAt: Date.now() };
66− if (update.name !== undefined) set.name = update.name;
67− if (update.value !== undefined) set.value = update.value;
68−
69− const updated = await db
70− .update(tables.secretMaterialsTable)
71− .set(set)
72− .where(eq(tables.secretMaterialsTable.id, id))
73− .returning({
74− id: tables.secretMaterialsTable.id,
75− name: tables.secretMaterialsTable.name,
76− purpose: tables.secretMaterialsTable.purpose,
77− createdAt: tables.secretMaterialsTable.createdAt,
78− updatedAt: tables.secretMaterialsTable.updatedAt,
79− });
80−
81− const row = firstOption(updated);
82− return Option.isSome(row) ? Option.some(row.value) : Option.none();
83− }),
84−
85− removeById: (id: SecretMaterial["id"]) =>
86− client.use("rows.secret_materials.remove", async (db) => {
87− const deleted = await db
88− .delete(tables.secretMaterialsTable)
89− .where(eq(tables.secretMaterialsTable.id, id))
90− .returning();
91−
92− return deleted.length > 0;
93− }),
94−
95− /**
96− * For each secret, find the sources that reference it via auth artifacts.
97− * Returns a map of secretId -> Array<{ sourceId, sourceName }>.
98− */
99− listLinkedSources: () =>
100− client.use("rows.secret_materials.list_linked_sources", async (db) => {
101− const rows = await db
102− .select({
103− artifactKind: tables.authArtifactsTable.artifactKind,
104− configJson: tables.authArtifactsTable.configJson,
105− sourceId: tables.authArtifactsTable.sourceId,
106− sourceName: tables.sourcesTable.name,
107− })
108− .from(tables.authArtifactsTable)
109− .innerJoin(
110− tables.sourcesTable,
111− and(
112− eq(tables.authArtifactsTable.workspaceId, tables.sourcesTable.workspaceId),
113− eq(tables.authArtifactsTable.sourceId, tables.sourcesTable.sourceId),
114− ),
115− );
116−
117− const result = new Map<string, Array<{ sourceId: string; sourceName: string }>>();
118−
119− for (const row of rows) {
120− const addLink = (secretId: string) => {
121− let links = result.get(secretId);
122− if (!links) {
123− links = [];
124− result.set(secretId, links);
125− }
126− // Avoid duplicate entries
127− if (!links.some((l) => l.sourceId === row.sourceId)) {
128− links.push({ sourceId: row.sourceId, sourceName: row.sourceName });
129− }
130− };
131−
132− for (const ref of authArtifactSecretRefs({
133− artifactKind: row.artifactKind,
134− configJson: row.configJson,
135− })) {
136− if (ref.providerId === "postgres") {
137− addLink(ref.handle);
138− }
139− }
140− }
141−
142− return result;
143− }),
144−});
packages/control-plane/src/persistence/repos/shared.ts+0−126
@@ -1,126 +0,0 @@
1−import { authArtifactSecretRefs, type AuthArtifact } from "#schema";
2−import { inArray } from "drizzle-orm";
3−import * as Option from "effect/Option";
4−
5−import type { DrizzleClient } from "../client";
6−import type { DrizzleTables } from "../schema";
7−
8−export const firstOption = <A>(rows: ReadonlyArray<A>): Option.Option<A> =>
9− rows.length > 0 ? Option.some(rows[0] as A) : Option.none<A>();
10−
11−export const withoutCreatedAt = <A extends { createdAt: unknown }>(
12− value: A,
13−): Omit<A, "createdAt"> => {
14− const { createdAt: _createdAt, ...rest } = value;
15− return rest;
16−};
17−
18−const POSTGRES_SECRET_PROVIDER_ID = "postgres";
19−
20−export const postgresSecretHandlesFromAuthArtifacts = (
21− artifacts: ReadonlyArray<Pick<AuthArtifact, "artifactKind" | "configJson">>,
22−): ReadonlyArray<string> => {
23− const handles = new Set<string>();
24−
25− for (const artifact of artifacts) {
26− for (const ref of authArtifactSecretRefs(artifact)) {
27− if (ref.providerId === POSTGRES_SECRET_PROVIDER_ID) {
28− handles.add(ref.handle);
29− }
30− }
31− }
32−
33− return [...handles];
34−};
35−
36−export const chunkArray = <A>(
37− values: ReadonlyArray<A>,
38− chunkSize: number,
39−): ReadonlyArray<ReadonlyArray<A>> => {
40− if (chunkSize <= 0) {
41− throw new Error(`chunkSize must be positive, received ${chunkSize}`);
42− }
43−
44− const chunks: Array<ReadonlyArray<A>> = [];
45− for (let index = 0; index < values.length; index += chunkSize) {
46− chunks.push(values.slice(index, index + chunkSize));
47− }
48−
49− return chunks;
50−};
51−
52−export const cleanupOrphanedSourceRecipes = async (input: {
53− tx: Parameters<Parameters<DrizzleClient["useTx"]>[1]>[0];
54− tables: DrizzleTables;
55− candidateRecipeIds: ReadonlyArray<string>;
56− candidateRecipeRevisionIds: ReadonlyArray<string>;
57−}) => {
58− const recipeIds = [...new Set(input.candidateRecipeIds)];
59− const recipeRevisionIds = [...new Set(input.candidateRecipeRevisionIds)];
60−
61− const remainingRevisionIds = recipeRevisionIds.length > 0
62− ? (
63− await input.tx
64− .select({ recipeRevisionId: input.tables.sourcesTable.recipeRevisionId })
65− .from(input.tables.sourcesTable)
66− .where(inArray(input.tables.sourcesTable.recipeRevisionId, recipeRevisionIds))
67− ).map((row) => row.recipeRevisionId)
68− : [];
69− const orphanRevisionIds = recipeRevisionIds.filter(
70− (recipeRevisionId) => !remainingRevisionIds.includes(recipeRevisionId),
71− );
72−
73− if (orphanRevisionIds.length > 0) {
74− await input.tx
75− .delete(input.tables.sourceRecipeDocumentsTable)
76− .where(inArray(input.tables.sourceRecipeDocumentsTable.recipeRevisionId, orphanRevisionIds));
77− await input.tx
78− .delete(input.tables.sourceRecipeSchemaBundlesTable)
79− .where(inArray(input.tables.sourceRecipeSchemaBundlesTable.recipeRevisionId, orphanRevisionIds));
80− await input.tx
81− .delete(input.tables.sourceRecipeOperationsTable)
82− .where(inArray(input.tables.sourceRecipeOperationsTable.recipeRevisionId, orphanRevisionIds));
83− }
84−
85− const remainingRecipeIds = recipeIds.length > 0
86− ? (
87− await input.tx
88− .select({ recipeId: input.tables.sourcesTable.recipeId })
89− .from(input.tables.sourcesTable)
90− .where(inArray(input.tables.sourcesTable.recipeId, recipeIds))
91− ).map((row) => row.recipeId)
92− : [];
93− const orphanRecipeIds = recipeIds.filter(
94− (recipeId) => !remainingRecipeIds.includes(recipeId),
95− );
96−
97− if (orphanRecipeIds.length === 0) {
98− return;
99− }
100−
101− const orphanRecipeRevisionIds = (
102− await input.tx
103− .select({ id: input.tables.sourceRecipeRevisionsTable.id })
104− .from(input.tables.sourceRecipeRevisionsTable)
105− .where(inArray(input.tables.sourceRecipeRevisionsTable.recipeId, orphanRecipeIds))
106− ).map((revision) => revision.id);
107−
108− if (orphanRecipeRevisionIds.length > 0) {
109− await input.tx
110− .delete(input.tables.sourceRecipeDocumentsTable)
111− .where(inArray(input.tables.sourceRecipeDocumentsTable.recipeRevisionId, orphanRecipeRevisionIds));
112− await input.tx
113− .delete(input.tables.sourceRecipeSchemaBundlesTable)
114− .where(inArray(input.tables.sourceRecipeSchemaBundlesTable.recipeRevisionId, orphanRecipeRevisionIds));
115− await input.tx
116− .delete(input.tables.sourceRecipeOperationsTable)
117− .where(inArray(input.tables.sourceRecipeOperationsTable.recipeRevisionId, orphanRecipeRevisionIds));
118− }
119−
120− await input.tx
121− .delete(input.tables.sourceRecipeRevisionsTable)
122− .where(inArray(input.tables.sourceRecipeRevisionsTable.recipeId, orphanRecipeIds));
123− await input.tx
124− .delete(input.tables.sourceRecipesTable)
125− .where(inArray(input.tables.sourceRecipesTable.id, orphanRecipeIds));
126−};
packages/control-plane/src/persistence/repos/source-auth-sessions-repo.ts+0−163
@@ -1,163 +0,0 @@
1−import {
2− type SourceAuthSession,
3− SourceAuthSessionSchema,
4−} from "#schema";
5−import * as Option from "effect/Option";
6−import { Schema } from "effect";
7−import { and, asc, eq, isNull } from "drizzle-orm";
8−
9−import type { DrizzleClient } from "../client";
10−import type { DrizzleTables } from "../schema";
11−import { firstOption, withoutCreatedAt } from "./shared";
12−
13−const decodeSourceAuthSession = Schema.decodeUnknownSync(SourceAuthSessionSchema);
14−
15−type Mutable<T> = { -readonly [K in keyof T]: T[K] };
16−type SourceAuthSessionPatch = Partial<
17− Omit<Mutable<SourceAuthSession>, "id" | "workspaceId" | "sourceId" | "createdAt">
18−>;
19−
20−export const createSourceAuthSessionsRepo = (
21− client: DrizzleClient,
22− tables: DrizzleTables,
23−) => ({
24− listAll: () =>
25− client.use("rows.source_auth_sessions.list_all", async (db) => {
26− const rows = await db
27− .select()
28− .from(tables.sourceAuthSessionsTable)
29− .orderBy(asc(tables.sourceAuthSessionsTable.updatedAt), asc(tables.sourceAuthSessionsTable.id));
30−
31− return rows.map((row) => decodeSourceAuthSession(row));
32− }),
33−
34− listByWorkspaceId: (workspaceId: SourceAuthSession["workspaceId"]) =>
35− client.use("rows.source_auth_sessions.list_by_workspace", async (db) => {
36− const rows = await db
37− .select()
38− .from(tables.sourceAuthSessionsTable)
39− .where(eq(tables.sourceAuthSessionsTable.workspaceId, workspaceId))
40− .orderBy(asc(tables.sourceAuthSessionsTable.updatedAt), asc(tables.sourceAuthSessionsTable.id));
41−
42− return rows.map((row) => decodeSourceAuthSession(row));
43− }),
44−
45− getById: (id: SourceAuthSession["id"]) =>
46− client.use("rows.source_auth_sessions.get_by_id", async (db) => {
47− const rows = await db
48− .select()
49− .from(tables.sourceAuthSessionsTable)
50− .where(eq(tables.sourceAuthSessionsTable.id, id))
51− .limit(1);
52−
53− const row = firstOption(rows);
54− return Option.isSome(row)
55− ? Option.some(decodeSourceAuthSession(row.value))
56− : Option.none<SourceAuthSession>();
57− }),
58−
59− getByState: (state: SourceAuthSession["state"]) =>
60− client.use("rows.source_auth_sessions.get_by_state", async (db) => {
61− const rows = await db
62− .select()
63− .from(tables.sourceAuthSessionsTable)
64− .where(eq(tables.sourceAuthSessionsTable.state, state))
65− .limit(1);
66−
67− const row = firstOption(rows);
68− return Option.isSome(row)
69− ? Option.some(decodeSourceAuthSession(row.value))
70− : Option.none<SourceAuthSession>();
71− }),
72−
73− getPendingByWorkspaceSourceAndActor: (input: {
74− workspaceId: SourceAuthSession["workspaceId"];
75− sourceId: SourceAuthSession["sourceId"];
76− actorAccountId: SourceAuthSession["actorAccountId"];
77− credentialSlot?: SourceAuthSession["credentialSlot"];
78− }) =>
79− client.use(
80− "rows.source_auth_sessions.get_pending_by_workspace_source_actor",
81− async (db) => {
82− const rows = await db
83− .select()
84− .from(tables.sourceAuthSessionsTable)
85− .where(
86− and(
87− eq(tables.sourceAuthSessionsTable.workspaceId, input.workspaceId),
88− eq(tables.sourceAuthSessionsTable.sourceId, input.sourceId),
89− input.credentialSlot === undefined
90− ? undefined
91− : eq(tables.sourceAuthSessionsTable.credentialSlot, input.credentialSlot),
92− input.actorAccountId === null
93− ? isNull(tables.sourceAuthSessionsTable.actorAccountId)
94− : eq(tables.sourceAuthSessionsTable.actorAccountId, input.actorAccountId),
95− eq(tables.sourceAuthSessionsTable.status, "pending"),
96− ),
97− )
98− .orderBy(
99− asc(tables.sourceAuthSessionsTable.updatedAt),
100− asc(tables.sourceAuthSessionsTable.id),
101− )
102− .limit(1);
103−
104− const row = firstOption(rows);
105− return Option.isSome(row)
106− ? Option.some(decodeSourceAuthSession(row.value))
107− : Option.none<SourceAuthSession>();
108− },
109− ),
110−
111− insert: (session: SourceAuthSession) =>
112− client.use("rows.source_auth_sessions.insert", async (db) => {
113− await db.insert(tables.sourceAuthSessionsTable).values(session);
114− }),
115−
116− update: (
117− id: SourceAuthSession["id"],
118− patch: SourceAuthSessionPatch,
119− ) =>
120− client.use("rows.source_auth_sessions.update", async (db) => {
121− const rows = await db
122− .update(tables.sourceAuthSessionsTable)
123− .set(patch)
124− .where(eq(tables.sourceAuthSessionsTable.id, id))
125− .returning();
126−
127− const row = firstOption(rows);
128− return Option.isSome(row)
129− ? Option.some(decodeSourceAuthSession(row.value))
130− : Option.none<SourceAuthSession>();
131− }),
132−
133− upsert: (session: SourceAuthSession) =>
134− client.use("rows.source_auth_sessions.upsert", async (db) => {
135− await db
136− .insert(tables.sourceAuthSessionsTable)
137− .values(session)
138− .onConflictDoUpdate({
139− target: [tables.sourceAuthSessionsTable.id],
140− set: {
141− ...withoutCreatedAt(session),
142− },
143− });
144− }),
145−
146− removeByWorkspaceAndSourceId: (
147− workspaceId: SourceAuthSession["workspaceId"],
148− sourceId: SourceAuthSession["sourceId"],
149− ) =>
150− client.use("rows.source_auth_sessions.remove_by_workspace_source", async (db) => {
151− const deleted = await db
152− .delete(tables.sourceAuthSessionsTable)
153− .where(
154− and(
155− eq(tables.sourceAuthSessionsTable.workspaceId, workspaceId),
156− eq(tables.sourceAuthSessionsTable.sourceId, sourceId),
157− ),
158− )
159− .returning();
160−
161− return deleted.length > 0;
162− }),
163−});
packages/control-plane/src/persistence/repos/source-oauth-clients-repo.ts+0−79
@@ -1,79 +0,0 @@
1−import {
2− type WorkspaceSourceOauthClient,
3− WorkspaceSourceOauthClientSchema,
4−} from "#schema";
5−import * as Option from "effect/Option";
6−import { Schema } from "effect";
7−import { and, eq } from "drizzle-orm";
8−
9−import type { DrizzleClient } from "../client";
10−import type { DrizzleTables } from "../schema";
11−import { firstOption, withoutCreatedAt } from "./shared";
12−
13−const decodeWorkspaceSourceOauthClient = Schema.decodeUnknownSync(
14− WorkspaceSourceOauthClientSchema,
15−);
16−
17−export const createSourceOauthClientsRepo = (
18− client: DrizzleClient,
19− tables: DrizzleTables,
20−) => ({
21− getByWorkspaceSourceAndProvider: (input: {
22− workspaceId: WorkspaceSourceOauthClient["workspaceId"];
23− sourceId: WorkspaceSourceOauthClient["sourceId"];
24− providerKey: string;
25− }) =>
26− client.use("rows.source_oauth_clients.get_by_workspace_source_provider", async (db) => {
27− const rows = await db
28− .select()
29− .from(tables.workspaceSourceOauthClientsTable)
30− .where(
31− and(
32− eq(tables.workspaceSourceOauthClientsTable.workspaceId, input.workspaceId),
33− eq(tables.workspaceSourceOauthClientsTable.sourceId, input.sourceId),
34− eq(tables.workspaceSourceOauthClientsTable.providerKey, input.providerKey),
35− ),
36− )
37− .limit(1);
38−
39− const row = firstOption(rows);
40− return Option.isSome(row)
41− ? Option.some(decodeWorkspaceSourceOauthClient(row.value))
42− : Option.none<WorkspaceSourceOauthClient>();
43− }),
44−
45− upsert: (oauthClient: WorkspaceSourceOauthClient) =>
46− client.use("rows.source_oauth_clients.upsert", async (db) => {
47− await db
48− .insert(tables.workspaceSourceOauthClientsTable)
49− .values(oauthClient)
50− .onConflictDoUpdate({
51− target: [
52− tables.workspaceSourceOauthClientsTable.workspaceId,
53− tables.workspaceSourceOauthClientsTable.sourceId,
54− tables.workspaceSourceOauthClientsTable.providerKey,
55− ],
56− set: {
57− ...withoutCreatedAt(oauthClient),
58− },
59− });
60− }),
61−
62− removeByWorkspaceAndSourceId: (input: {
63− workspaceId: WorkspaceSourceOauthClient["workspaceId"];
64− sourceId: WorkspaceSourceOauthClient["sourceId"];
65− }) =>
66− client.use("rows.source_oauth_clients.remove_by_workspace_source", async (db) => {
67− const deleted = await db
68− .delete(tables.workspaceSourceOauthClientsTable)
69− .where(
70− and(
71− eq(tables.workspaceSourceOauthClientsTable.workspaceId, input.workspaceId),
72− eq(tables.workspaceSourceOauthClientsTable.sourceId, input.sourceId),
73− ),
74− )
75− .returning();
76−
77− return deleted.length;
78− }),
79−});
packages/control-plane/src/persistence/repos/source-recipe-documents-repo.ts+0−85
@@ -1,85 +0,0 @@
1−import {
2− type StoredSourceRecipeDocumentRecord,
3− StoredSourceRecipeDocumentRecordSchema,
4−} from "#schema";
5−import { Schema } from "effect";
6−import { asc, eq, inArray } from "drizzle-orm";
7−
8−import type { DrizzleClient } from "../client";
9−import type { DrizzleTables } from "../schema";
10−import { chunkArray } from "./shared";
11−
12−const decodeStoredSourceRecipeDocumentRecord = Schema.decodeUnknownSync(
13− StoredSourceRecipeDocumentRecordSchema,
14−);
15−
16−const RECIPE_DOCUMENT_INSERT_BATCH_SIZE = 500;
17−
18−export const createSourceRecipeDocumentsRepo = (
19− client: DrizzleClient,
20− tables: DrizzleTables,
21−) => ({
22− listByRevisionId: (recipeRevisionId: StoredSourceRecipeDocumentRecord["recipeRevisionId"]) =>
23− client.use("rows.source_recipe_documents.list_by_revision", async (db) => {
24− const rows = await db
25− .select()
26− .from(tables.sourceRecipeDocumentsTable)
27− .where(eq(tables.sourceRecipeDocumentsTable.recipeRevisionId, recipeRevisionId))
28− .orderBy(
29− asc(tables.sourceRecipeDocumentsTable.documentKind),
30− asc(tables.sourceRecipeDocumentsTable.documentKey),
31− );
32−
33− return rows.map((row) => decodeStoredSourceRecipeDocumentRecord(row));
34− }),
35−
36− listByRevisionIds: (
37− recipeRevisionIds: readonly StoredSourceRecipeDocumentRecord["recipeRevisionId"][],
38− ) =>
39− client.use("rows.source_recipe_documents.list_by_revisions", async (db) => {
40− if (recipeRevisionIds.length === 0) {
41− return [] as StoredSourceRecipeDocumentRecord[];
42− }
43−
44− const rows = await db
45− .select()
46− .from(tables.sourceRecipeDocumentsTable)
47− .where(inArray(tables.sourceRecipeDocumentsTable.recipeRevisionId, [...recipeRevisionIds]))
48− .orderBy(
49− asc(tables.sourceRecipeDocumentsTable.recipeRevisionId),
50− asc(tables.sourceRecipeDocumentsTable.documentKind),
51− asc(tables.sourceRecipeDocumentsTable.documentKey),
52− );
53−
54− return rows.map((row) => decodeStoredSourceRecipeDocumentRecord(row));
55− }),
56−
57− replaceForRevision: (input: {
58− recipeRevisionId: StoredSourceRecipeDocumentRecord["recipeRevisionId"];
59− documents: readonly StoredSourceRecipeDocumentRecord[];
60− }) =>
61− client.useTx("rows.source_recipe_documents.replace_for_revision", async (tx) => {
62− await tx
63− .delete(tables.sourceRecipeDocumentsTable)
64− .where(eq(tables.sourceRecipeDocumentsTable.recipeRevisionId, input.recipeRevisionId));
65−
66− if (input.documents.length > 0) {
67− for (const batch of chunkArray(
68− input.documents,
69− RECIPE_DOCUMENT_INSERT_BATCH_SIZE,
70− )) {
71− await tx.insert(tables.sourceRecipeDocumentsTable).values([...batch]);
72− }
73− }
74− }),
75−
76− removeByRevisionId: (recipeRevisionId: StoredSourceRecipeDocumentRecord["recipeRevisionId"]) =>
77− client.use("rows.source_recipe_documents.remove_by_revision", async (db) => {
78− const deleted = await db
79− .delete(tables.sourceRecipeDocumentsTable)
80− .where(eq(tables.sourceRecipeDocumentsTable.recipeRevisionId, recipeRevisionId))
81− .returning();
82−
83− return deleted.length;
84− }),
85−});
packages/control-plane/src/persistence/repos/source-recipe-operations-repo.ts+0−86
@@ -1,86 +0,0 @@
1−import {
2− type StoredSourceRecipeOperationRecord,
3− StoredSourceRecipeOperationRecordSchema,
4−} from "#schema";
5−import { Schema } from "effect";
6−import { asc, eq, inArray } from "drizzle-orm";
7−
8−import type { DrizzleClient } from "../client";
9−import type { DrizzleTables } from "../schema";
10−import { chunkArray } from "./shared";
11−
12−const decodeStoredSourceRecipeOperationRecord = Schema.decodeUnknownSync(
13− StoredSourceRecipeOperationRecordSchema,
14−);
15−
16−// Keep wide recipe-operation inserts well below Postgres/PGlite parameter limits.
17−const RECIPE_OPERATION_INSERT_BATCH_SIZE = 200;
18−
19−export const createSourceRecipeOperationsRepo = (
20− client: DrizzleClient,
21− tables: DrizzleTables,
22−) => ({
23− listByRevisionId: (recipeRevisionId: StoredSourceRecipeOperationRecord["recipeRevisionId"]) =>
24− client.use("rows.source_recipe_operations.list_by_revision", async (db) => {
25− const rows = await db
26− .select()
27− .from(tables.sourceRecipeOperationsTable)
28− .where(eq(tables.sourceRecipeOperationsTable.recipeRevisionId, recipeRevisionId))
29− .orderBy(
30− asc(tables.sourceRecipeOperationsTable.toolId),
31− asc(tables.sourceRecipeOperationsTable.id),
32− );
33−
34− return rows.map((row) => decodeStoredSourceRecipeOperationRecord(row));
35− }),
36−
37− listByRevisionIds: (
38− recipeRevisionIds: readonly StoredSourceRecipeOperationRecord["recipeRevisionId"][],
39− ) =>
40− client.use("rows.source_recipe_operations.list_by_revisions", async (db) => {
41− if (recipeRevisionIds.length === 0) {
42− return [] as StoredSourceRecipeOperationRecord[];
43− }
44−
45− const rows = await db
46− .select()
47− .from(tables.sourceRecipeOperationsTable)
48− .where(inArray(tables.sourceRecipeOperationsTable.recipeRevisionId, [...recipeRevisionIds]))
49− .orderBy(
50− asc(tables.sourceRecipeOperationsTable.recipeRevisionId),
51− asc(tables.sourceRecipeOperationsTable.toolId),
52− asc(tables.sourceRecipeOperationsTable.id),
53− );
54−
55− return rows.map((row) => decodeStoredSourceRecipeOperationRecord(row));
56− }),
57−
58− replaceForRevision: (input: {
59− recipeRevisionId: StoredSourceRecipeOperationRecord["recipeRevisionId"];
60− operations: readonly StoredSourceRecipeOperationRecord[];
61− }) =>
62− client.useTx("rows.source_recipe_operations.replace_for_revision", async (tx) => {
63− await tx
64− .delete(tables.sourceRecipeOperationsTable)
65− .where(eq(tables.sourceRecipeOperationsTable.recipeRevisionId, input.recipeRevisionId));
66−
67− if (input.operations.length > 0) {
68− for (const batch of chunkArray(
69− input.operations,
70− RECIPE_OPERATION_INSERT_BATCH_SIZE,
71− )) {
72− await tx.insert(tables.sourceRecipeOperationsTable).values([...batch]);
73− }
74− }
75− }),
76−
77− removeByRevisionId: (recipeRevisionId: StoredSourceRecipeOperationRecord["recipeRevisionId"]) =>
78− client.use("rows.source_recipe_operations.remove_by_revision", async (db) => {
79− const deleted = await db
80− .delete(tables.sourceRecipeOperationsTable)
81− .where(eq(tables.sourceRecipeOperationsTable.recipeRevisionId, recipeRevisionId))
82− .returning();
83−
84− return deleted.length;
85− }),
86−});
packages/control-plane/src/persistence/repos/source-recipe-revisions-repo.ts+0−157
@@ -1,157 +0,0 @@
1−import {
2− type StoredSourceRecipeRevisionRecord,
3− StoredSourceRecipeRevisionRecordSchema,
4−} from "#schema";
5−import * as Option from "effect/Option";
6−import { Schema } from "effect";
7−import { asc, desc, eq, inArray, and } from "drizzle-orm";
8−
9−import type { DrizzleClient } from "../client";
10−import type { DrizzleTables } from "../schema";
11−import { firstOption, withoutCreatedAt } from "./shared";
12−
13−const decodeStoredSourceRecipeRevisionRecord = Schema.decodeUnknownSync(
14− StoredSourceRecipeRevisionRecordSchema,
15−);
16−
17−type Mutable<T> = { -readonly [K in keyof T]: T[K] };
18−type RecipeRevisionPatch = Partial<
19− Omit<Mutable<StoredSourceRecipeRevisionRecord>, "id" | "recipeId" | "revisionNumber" | "createdAt">
20−>;
21−
22−export const createSourceRecipeRevisionsRepo = (
23− client: DrizzleClient,
24− tables: DrizzleTables,
25−) => ({
26− listByRecipeId: (recipeId: StoredSourceRecipeRevisionRecord["recipeId"]) =>
27− client.use("rows.source_recipe_revisions.list_by_recipe", async (db) => {
28− const rows = await db
29− .select()
30− .from(tables.sourceRecipeRevisionsTable)
31− .where(eq(tables.sourceRecipeRevisionsTable.recipeId, recipeId))
32− .orderBy(
33− asc(tables.sourceRecipeRevisionsTable.revisionNumber),
34− asc(tables.sourceRecipeRevisionsTable.createdAt),
35− );
36−
37− return rows.map((row) => decodeStoredSourceRecipeRevisionRecord(row));
38− }),
39−
40− listByIds: (ids: readonly StoredSourceRecipeRevisionRecord["id"][]) =>
41− client.use("rows.source_recipe_revisions.list_by_ids", async (db) => {
42− if (ids.length === 0) {
43− return [] as StoredSourceRecipeRevisionRecord[];
44− }
45−
46− const rows = await db
47− .select()
48− .from(tables.sourceRecipeRevisionsTable)
49− .where(inArray(tables.sourceRecipeRevisionsTable.id, [...ids]))
50− .orderBy(
51− asc(tables.sourceRecipeRevisionsTable.recipeId),
52− asc(tables.sourceRecipeRevisionsTable.revisionNumber),
53− );
54−
55− return rows.map((row) => decodeStoredSourceRecipeRevisionRecord(row));
56− }),
57−
58− getById: (id: StoredSourceRecipeRevisionRecord["id"]) =>
59− client.use("rows.source_recipe_revisions.get_by_id", async (db) => {
60− const rows = await db
61− .select()
62− .from(tables.sourceRecipeRevisionsTable)
63− .where(eq(tables.sourceRecipeRevisionsTable.id, id))
64− .limit(1);
65−
66− const row = firstOption(rows);
67− return Option.isSome(row)
68− ? Option.some(decodeStoredSourceRecipeRevisionRecord(row.value))
69− : Option.none<StoredSourceRecipeRevisionRecord>();
70− }),
71−
72− getByRecipeAndMaterializationHash: (input: {
73− recipeId: StoredSourceRecipeRevisionRecord["recipeId"];
74− materializationHash: string;
75− }) =>
76− client.use("rows.source_recipe_revisions.get_by_recipe_materialization", async (db) => {
77− const rows = await db
78− .select()
79− .from(tables.sourceRecipeRevisionsTable)
80− .where(
81− and(
82− eq(tables.sourceRecipeRevisionsTable.recipeId, input.recipeId),
83− eq(tables.sourceRecipeRevisionsTable.materializationHash, input.materializationHash),
84− ),
85− )
86− .limit(1);
87−
88− const row = firstOption(rows);
89− return Option.isSome(row)
90− ? Option.some(decodeStoredSourceRecipeRevisionRecord(row.value))
91− : Option.none<StoredSourceRecipeRevisionRecord>();
92− }),
93−
94− nextRevisionNumber: (recipeId: StoredSourceRecipeRevisionRecord["recipeId"]) =>
95− client.use("rows.source_recipe_revisions.next_revision_number", async (db) => {
96− const rows = await db
97− .select({
98− revisionNumber: tables.sourceRecipeRevisionsTable.revisionNumber,
99− })
100− .from(tables.sourceRecipeRevisionsTable)
101− .where(eq(tables.sourceRecipeRevisionsTable.recipeId, recipeId))
102− .orderBy(desc(tables.sourceRecipeRevisionsTable.revisionNumber))
103− .limit(1);
104−
105− return Number(rows[0]?.revisionNumber ?? 0) + 1;
106− }),
107−
108− upsert: (revision: StoredSourceRecipeRevisionRecord) =>
109− client.use("rows.source_recipe_revisions.upsert", async (db) => {
110− await db
111− .insert(tables.sourceRecipeRevisionsTable)
112− .values(revision)
113− .onConflictDoUpdate({
114− target: [tables.sourceRecipeRevisionsTable.id],
115− set: {
116− ...withoutCreatedAt(revision),
117− },
118− });
119− }),
120−
121− update: (
122− id: StoredSourceRecipeRevisionRecord["id"],
123− patch: RecipeRevisionPatch,
124− ) =>
125− client.use("rows.source_recipe_revisions.update", async (db) => {
126− const rows = await db
127− .update(tables.sourceRecipeRevisionsTable)
128− .set(patch)
129− .where(eq(tables.sourceRecipeRevisionsTable.id, id))
130− .returning();
131−
132− const row = firstOption(rows);
133− return Option.isSome(row)
134− ? Option.some(decodeStoredSourceRecipeRevisionRecord(row.value))
135− : Option.none<StoredSourceRecipeRevisionRecord>();
136− }),
137−
138− removeByRecipeId: (recipeId: StoredSourceRecipeRevisionRecord["recipeId"]) =>
139− client.use("rows.source_recipe_revisions.remove_by_recipe", async (db) => {
140− const deleted = await db
141− .delete(tables.sourceRecipeRevisionsTable)
142− .where(eq(tables.sourceRecipeRevisionsTable.recipeId, recipeId))
143− .returning();
144−
145− return deleted.length;
146− }),
147−
148− removeById: (id: StoredSourceRecipeRevisionRecord["id"]) =>
149− client.use("rows.source_recipe_revisions.remove_by_id", async (db) => {
150− const deleted = await db
151− .delete(tables.sourceRecipeRevisionsTable)
152− .where(eq(tables.sourceRecipeRevisionsTable.id, id))
153− .returning();
154−
155− return deleted.length > 0;
156− }),
157−});
packages/control-plane/src/persistence/repos/source-recipe-schema-bundles-repo.ts+0−106
@@ -1,106 +0,0 @@
1−import {
2− type StoredSourceRecipeSchemaBundleRecord,
3− StoredSourceRecipeSchemaBundleRecordSchema,
4−} from "#schema";
5−import * as Option from "effect/Option";
6−import { Schema } from "effect";
7−import { asc, eq, inArray } from "drizzle-orm";
8−
9−import type { DrizzleClient } from "../client";
10−import type { DrizzleTables } from "../schema";
11−import { firstOption, withoutCreatedAt } from "./shared";
12−
13−const decodeStoredSourceRecipeSchemaBundleRecord = Schema.decodeUnknownSync(
14− StoredSourceRecipeSchemaBundleRecordSchema,
15−);
16−
17−export const createSourceRecipeSchemaBundlesRepo = (
18− client: DrizzleClient,
19− tables: DrizzleTables,
20−) => ({
21− getById: (id: StoredSourceRecipeSchemaBundleRecord["id"]) =>
22− client.use("rows.source_recipe_schema_bundles.get_by_id", async (db) => {
23− const rows = await db
24− .select()
25− .from(tables.sourceRecipeSchemaBundlesTable)
26− .where(eq(tables.sourceRecipeSchemaBundlesTable.id, id))
27− .limit(1);
28−
29− const row = firstOption(rows);
30− return Option.isSome(row)
31− ? Option.some(decodeStoredSourceRecipeSchemaBundleRecord(row.value))
32− : Option.none<StoredSourceRecipeSchemaBundleRecord>();
33− }),
34−
35− listByRevisionId: (recipeRevisionId: StoredSourceRecipeSchemaBundleRecord["recipeRevisionId"]) =>
36− client.use("rows.source_recipe_schema_bundles.list_by_revision", async (db) => {
37− const rows = await db
38− .select()
39− .from(tables.sourceRecipeSchemaBundlesTable)
40− .where(eq(tables.sourceRecipeSchemaBundlesTable.recipeRevisionId, recipeRevisionId))
41− .orderBy(
42− asc(tables.sourceRecipeSchemaBundlesTable.bundleKind),
43− asc(tables.sourceRecipeSchemaBundlesTable.createdAt),
44− );
45−
46− return rows.map((row) => decodeStoredSourceRecipeSchemaBundleRecord(row));
47− }),
48−
49− listByRevisionIds: (
50− recipeRevisionIds: readonly StoredSourceRecipeSchemaBundleRecord["recipeRevisionId"][],
51− ) =>
52− client.use("rows.source_recipe_schema_bundles.list_by_revisions", async (db) => {
53− if (recipeRevisionIds.length === 0) {
54− return [] as StoredSourceRecipeSchemaBundleRecord[];
55− }
56−
57− const rows = await db
58− .select()
59− .from(tables.sourceRecipeSchemaBundlesTable)
60− .where(inArray(tables.sourceRecipeSchemaBundlesTable.recipeRevisionId, [...recipeRevisionIds]))
61− .orderBy(
62− asc(tables.sourceRecipeSchemaBundlesTable.recipeRevisionId),
63− asc(tables.sourceRecipeSchemaBundlesTable.bundleKind),
64− asc(tables.sourceRecipeSchemaBundlesTable.createdAt),
65− );
66−
67− return rows.map((row) => decodeStoredSourceRecipeSchemaBundleRecord(row));
68− }),
69−
70− replaceForRevision: (input: {
71− recipeRevisionId: StoredSourceRecipeSchemaBundleRecord["recipeRevisionId"];
72− bundles: readonly StoredSourceRecipeSchemaBundleRecord[];
73− }) =>
74− client.useTx("rows.source_recipe_schema_bundles.replace_for_revision", async (tx) => {
75− await tx
76− .delete(tables.sourceRecipeSchemaBundlesTable)
77− .where(eq(tables.sourceRecipeSchemaBundlesTable.recipeRevisionId, input.recipeRevisionId));
78−
79− if (input.bundles.length > 0) {
80− await tx.insert(tables.sourceRecipeSchemaBundlesTable).values([...input.bundles]);
81− }
82− }),
83−
84− upsert: (bundle: StoredSourceRecipeSchemaBundleRecord) =>
85− client.use("rows.source_recipe_schema_bundles.upsert", async (db) => {
86− await db
87− .insert(tables.sourceRecipeSchemaBundlesTable)
88− .values(bundle)
89− .onConflictDoUpdate({
90− target: [tables.sourceRecipeSchemaBundlesTable.id],
91− set: {
92− ...withoutCreatedAt(bundle),
93− },
94− });
95− }),
96−
97− removeByRevisionId: (recipeRevisionId: StoredSourceRecipeSchemaBundleRecord["recipeRevisionId"]) =>
98− client.use("rows.source_recipe_schema_bundles.remove_by_revision", async (db) => {
99− const deleted = await db
100− .delete(tables.sourceRecipeSchemaBundlesTable)
101− .where(eq(tables.sourceRecipeSchemaBundlesTable.recipeRevisionId, recipeRevisionId))
102− .returning();
103−
104− return deleted.length;
105− }),
106−});
packages/control-plane/src/persistence/repos/source-recipes-repo.ts+0−67
@@ -1,67 +0,0 @@
1−import {
2− type StoredSourceRecipeRecord,
3− StoredSourceRecipeRecordSchema,
4−} from "#schema";
5−import * as Option from "effect/Option";
6−import { Schema } from "effect";
7−import { asc, eq } from "drizzle-orm";
8−
9−import type { DrizzleClient } from "../client";
10−import type { DrizzleTables } from "../schema";
11−import { firstOption, withoutCreatedAt } from "./shared";
12−
13−const decodeStoredSourceRecipeRecord = Schema.decodeUnknownSync(
14− StoredSourceRecipeRecordSchema,
15−);
16−
17−export const createSourceRecipesRepo = (
18− client: DrizzleClient,
19− tables: DrizzleTables,
20−) => ({
21− listByUpdatedAt: () =>
22− client.use("rows.source_recipes.list", async (db) => {
23− const rows = await db
24− .select()
25− .from(tables.sourceRecipesTable)
26− .orderBy(asc(tables.sourceRecipesTable.updatedAt), asc(tables.sourceRecipesTable.id));
27−
28− return rows.map((row) => decodeStoredSourceRecipeRecord(row));
29− }),
30−
31− getById: (id: StoredSourceRecipeRecord["id"]) =>
32− client.use("rows.source_recipes.get_by_id", async (db) => {
33− const rows = await db
34− .select()
35− .from(tables.sourceRecipesTable)
36− .where(eq(tables.sourceRecipesTable.id, id))
37− .limit(1);
38−
39− const row = firstOption(rows);
40− return Option.isSome(row)
41− ? Option.some(decodeStoredSourceRecipeRecord(row.value))
42− : Option.none<StoredSourceRecipeRecord>();
43− }),
44−
45− upsert: (recipe: StoredSourceRecipeRecord) =>
46− client.use("rows.source_recipes.upsert", async (db) => {
47− await db
48− .insert(tables.sourceRecipesTable)
49− .values(recipe)
50− .onConflictDoUpdate({
51− target: [tables.sourceRecipesTable.id],
52− set: {
53− ...withoutCreatedAt(recipe),
54− },
55− });
56− }),
57−
58− removeById: (id: StoredSourceRecipeRecord["id"]) =>
59− client.use("rows.source_recipes.remove", async (db) => {
60− const deleted = await db
61− .delete(tables.sourceRecipesTable)
62− .where(eq(tables.sourceRecipesTable.id, id))
63− .returning();
64−
65− return deleted.length > 0;
66− }),
67−});
packages/control-plane/src/persistence/repos/sources-repo.ts+0−154
@@ -1,154 +0,0 @@
1−import {
2− type StoredSourceRecord,
3− StoredSourceRecordSchema,
4−} from "#schema";
5−import * as Option from "effect/Option";
6−import { Schema } from "effect";
7−import { and, asc, count, eq } from "drizzle-orm";
8−
9−import type { DrizzleClient } from "../client";
10−import type { DrizzleTables } from "../schema";
11−import { firstOption } from "./shared";
12−
13−const decodeStoredSourceRecord = Schema.decodeUnknownSync(StoredSourceRecordSchema);
14−const encodeStoredSourceRecord = Schema.encodeSync(StoredSourceRecordSchema);
15−
16−const toSourceUpdateSet = (
17− patch: Partial<Omit<StoredSourceRecord, "id" | "workspaceId" | "createdAt">>,
18−): Partial<DrizzleTables["sourcesTable"]["$inferInsert"]> => {
19− const set: Partial<DrizzleTables["sourcesTable"]["$inferInsert"]> = {};
20−
21− if (patch.recipeId !== undefined) set.recipeId = patch.recipeId;
22− if (patch.recipeRevisionId !== undefined) set.recipeRevisionId = patch.recipeRevisionId;
23− if (patch.name !== undefined) set.name = patch.name;
24− if (patch.kind !== undefined) set.kind = patch.kind;
25− if (patch.endpoint !== undefined) set.endpoint = patch.endpoint;
26− if (patch.status !== undefined) set.status = patch.status;
27− if (patch.enabled !== undefined) set.enabled = patch.enabled;
28− if (patch.namespace !== undefined) set.namespace = patch.namespace;
29− if (patch.importAuthPolicy !== undefined) set.importAuthPolicy = patch.importAuthPolicy;
30− if (patch.bindingConfigJson !== undefined) set.bindingConfigJson = patch.bindingConfigJson;
31− if (patch.sourceHash !== undefined) set.sourceHash = patch.sourceHash;
32− if (patch.lastError !== undefined) set.lastError = patch.lastError;
33− if (patch.updatedAt !== undefined) set.updatedAt = patch.updatedAt;
34−
35− return set;
36−};
37−
38−export const createSourcesRepo = (
39− client: DrizzleClient,
40− tables: DrizzleTables,
41−) => ({
42− listAll: () =>
43− client.use("rows.sources.list_all", async (db) => {
44− const rows = await db
45− .select()
46− .from(tables.sourcesTable)
47− .orderBy(
48− asc(tables.sourcesTable.workspaceId),
49− asc(tables.sourcesTable.updatedAt),
50− asc(tables.sourcesTable.sourceId),
51− );
52−
53− return rows.map((row) => decodeStoredSourceRecord(row));
54− }),
55−
56− listByWorkspaceId: (workspaceId: StoredSourceRecord["workspaceId"]) =>
57− client.use("rows.sources.list_by_workspace", async (db) => {
58− const rows = await db
59− .select()
60− .from(tables.sourcesTable)
61− .where(eq(tables.sourcesTable.workspaceId, workspaceId))
62− .orderBy(asc(tables.sourcesTable.updatedAt), asc(tables.sourcesTable.sourceId));
63−
64− return rows.map((row) => decodeStoredSourceRecord(row));
65− }),
66−
67− getByWorkspaceAndId: (
68− workspaceId: StoredSourceRecord["workspaceId"],
69− sourceId: StoredSourceRecord["id"],
70− ) =>
71− client.use("rows.sources.get_by_workspace_and_id", async (db) => {
72− const rows = await db
73− .select()
74− .from(tables.sourcesTable)
75− .where(
76− and(
77− eq(tables.sourcesTable.workspaceId, workspaceId),
78− eq(tables.sourcesTable.sourceId, sourceId),
79− ),
80− )
81− .limit(1);
82−
83− const row = firstOption(rows);
84− return Option.isSome(row)
85− ? Option.some(decodeStoredSourceRecord(row.value))
86− : Option.none<StoredSourceRecord>();
87− }),
88−
89− countByRecipeId: (recipeId: StoredSourceRecord["recipeId"]) =>
90− client.use("rows.sources.count_by_recipe", async (db) => {
91− const rows = await db
92− .select({ value: count() })
93− .from(tables.sourcesTable)
94− .where(eq(tables.sourcesTable.recipeId, recipeId));
95−
96− return Number(rows[0]?.value ?? 0);
97− }),
98−
99− countByRecipeRevisionId: (recipeRevisionId: StoredSourceRecord["recipeRevisionId"]) =>
100− client.use("rows.sources.count_by_recipe_revision", async (db) => {
101− const rows = await db
102− .select({ value: count() })
103− .from(tables.sourcesTable)
104− .where(eq(tables.sourcesTable.recipeRevisionId, recipeRevisionId));
105−
106− return Number(rows[0]?.value ?? 0);
107− }),
108−
109− insert: (source: StoredSourceRecord) =>
110− client.use("rows.sources.insert", async (db) => {
111− await db.insert(tables.sourcesTable).values(encodeStoredSourceRecord(source));
112− }),
113−
114− update: (
115− workspaceId: StoredSourceRecord["workspaceId"],
116− sourceId: StoredSourceRecord["id"],
117− patch: Partial<Omit<StoredSourceRecord, "id" | "workspaceId" | "createdAt">>,
118− ) =>
119− client.use("rows.sources.update", async (db) => {
120− const rows = await db
121− .update(tables.sourcesTable)
122− .set(toSourceUpdateSet(patch))
123− .where(
124− and(
125− eq(tables.sourcesTable.workspaceId, workspaceId),
126− eq(tables.sourcesTable.sourceId, sourceId),
127− ),
128− )
129− .returning();
130−
131− const row = firstOption(rows);
132− return Option.isSome(row)
133− ? Option.some(decodeStoredSourceRecord(row.value))
134− : Option.none<StoredSourceRecord>();
135− }),
136−
137− removeByWorkspaceAndId: (
138− workspaceId: StoredSourceRecord["workspaceId"],
139− sourceId: StoredSourceRecord["id"],
140− ) =>
141− client.useTx("rows.sources.remove", async (tx) => {
142− const deleted = await tx
143− .delete(tables.sourcesTable)
144− .where(
145− and(
146− eq(tables.sourcesTable.workspaceId, workspaceId),
147− eq(tables.sourcesTable.sourceId, sourceId),
148− ),
149− )
150− .returning();
151−
152− return deleted.length > 0;
153− }),
154−});
packages/control-plane/src/persistence/schema.ts+0−550
@@ -1,550 +0,0 @@
1−import {
2− bigint,
3− boolean,
4− check,
5− index,
6− pgTable,
7− primaryKey,
8− text,
9− uniqueIndex,
10−} from "drizzle-orm/pg-core";
11−import { sql } from "drizzle-orm";
12−
13−export const tableNames = {
14− accounts: "accounts",
15− sources: "sources",
16− sourceRecipes: "source_recipes",
17− sourceRecipeRevisions: "source_recipe_revisions",
18− sourceRecipeDocuments: "source_recipe_documents",
19− sourceRecipeSchemaBundles: "source_recipe_schema_bundles",
20− sourceRecipeOperations: "source_recipe_operations",
21− codeMigrations: "control_plane_code_migrations",
22− authArtifacts: "workspace_source_auth_artifacts",
23− authLeases: "workspace_source_auth_leases",
24− workspaceSourceOauthClients: "workspace_source_oauth_clients",
25− secretMaterials: "secret_materials",
26− sourceAuthSessions: "source_auth_sessions",
27− executions: "executions",
28− executionInteractions: "execution_interactions",
29− executionSteps: "execution_steps",
30−} as const;
31−
32−export const accountsTable = pgTable(
33− tableNames.accounts,
34− {
35− id: text("id").notNull().primaryKey(),
36− provider: text("provider").notNull(),
37− subject: text("subject").notNull(),
38− email: text("email"),
39− displayName: text("display_name"),
40− createdAt: bigint("created_at", { mode: "number" }).notNull(),
41− updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
42− },
43− (table) => [
44− uniqueIndex("accounts_provider_subject_idx").on(table.provider, table.subject),
45− index("accounts_updated_idx").on(table.updatedAt, table.id),
46− check(
47− "accounts_provider_check",
48− sql`${table.provider} in ('local', 'workos', 'service')`,
49− ),
50− ],
51−);
52−
53−export const sourcesTable = pgTable(
54− tableNames.sources,
55− {
56− workspaceId: text("workspace_id").notNull(),
57− sourceId: text("source_id").notNull(),
58− recipeId: text("recipe_id").notNull(),
59− recipeRevisionId: text("recipe_revision_id").notNull(),
60− name: text("name").notNull(),
61− kind: text("kind").notNull(),
62− endpoint: text("endpoint").notNull(),
63− status: text("status").notNull(),
64− enabled: boolean("enabled").notNull(),
65− namespace: text("namespace"),
66− importAuthPolicy: text("import_auth_policy").notNull(),
67− bindingConfigJson: text("binding_config_json").notNull(),
68− sourceHash: text("source_hash"),
69− lastError: text("last_error"),
70− createdAt: bigint("created_at", { mode: "number" }).notNull(),
71− updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
72− },
73− (table) => [
74− primaryKey({
75− columns: [table.workspaceId, table.sourceId],
76− }),
77− index("sources_workspace_updated_idx").on(
78− table.workspaceId,
79− table.updatedAt,
80− table.sourceId,
81− ),
82− index("sources_workspace_recipe_idx").on(
83− table.workspaceId,
84− table.recipeId,
85− table.updatedAt,
86− table.sourceId,
87− ),
88− uniqueIndex("sources_workspace_name_idx").on(table.workspaceId, table.name),
89− check(
90− "sources_status_check",
91− sql`${table.status} in ('draft', 'probing', 'auth_required', 'connected', 'error')`,
92− ),
93− check(
94− "sources_import_auth_policy_check",
95− sql`${table.importAuthPolicy} in ('none', 'reuse_runtime', 'separate')`,
96− ),
97− ],
98−);
99−
100−export const sourceRecipesTable = pgTable(
101− tableNames.sourceRecipes,
102− {
103− id: text("id").notNull().primaryKey(),
104− kind: text("kind").notNull(),
105− adapterKey: text("adapter_key").notNull(),
106− providerKey: text("provider_key").notNull(),
107− name: text("name").notNull(),
108− summary: text("summary"),
109− visibility: text("visibility").notNull(),
110− latestRevisionId: text("latest_revision_id").notNull(),
111− createdAt: bigint("created_at", { mode: "number" }).notNull(),
112− updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
113− },
114− (table) => [
115− index("source_recipes_provider_updated_idx").on(
116− table.providerKey,
117− table.updatedAt,
118− table.id,
119− ),
120− index("source_recipes_visibility_updated_idx").on(
121− table.visibility,
122− table.updatedAt,
123− table.id,
124− ),
125− check(
126− "source_recipes_kind_check",
127− sql`${table.kind} in ('http_api', 'mcp', 'internal')`,
128− ),
129− check(
130− "source_recipes_visibility_check",
131− sql`${table.visibility} in ('private', 'workspace', 'organization', 'public')`,
132− ),
133− ],
134−);
135−
136−export const sourceRecipeRevisionsTable = pgTable(
137− tableNames.sourceRecipeRevisions,
138− {
139− id: text("id").notNull().primaryKey(),
140− recipeId: text("recipe_id").notNull(),
141− revisionNumber: bigint("revision_number", { mode: "number" }).notNull(),
142− sourceConfigJson: text("source_config_json").notNull(),
143− manifestJson: text("manifest_json"),
144− manifestHash: text("manifest_hash"),
145− materializationHash: text("materialization_hash"),
146− createdAt: bigint("created_at", { mode: "number" }).notNull(),
147− updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
148− },
149− (table) => [
150− uniqueIndex("source_recipe_revisions_recipe_revision_idx").on(
151− table.recipeId,
152− table.revisionNumber,
153− ),
154− uniqueIndex("source_recipe_revisions_recipe_materialization_idx").on(
155− table.recipeId,
156− table.materializationHash,
157− ),
158− index("source_recipe_revisions_recipe_manifest_idx").on(
159− table.recipeId,
160− table.manifestHash,
161− ),
162− index("source_recipe_revisions_recipe_created_idx").on(
163− table.recipeId,
164− table.createdAt,
165− table.id,
166− ),
167− ],
168−);
169−
170−export const sourceRecipeDocumentsTable = pgTable(
171− tableNames.sourceRecipeDocuments,
172− {
173− id: text("id").notNull().primaryKey(),
174− recipeRevisionId: text("recipe_revision_id").notNull(),
175− documentKind: text("document_kind").notNull(),
176− documentKey: text("document_key").notNull(),
177− contentText: text("content_text").notNull(),
178− contentHash: text("content_hash").notNull(),
179− fetchedAt: bigint("fetched_at", { mode: "number" }),
180− createdAt: bigint("created_at", { mode: "number" }).notNull(),
181− updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
182− },
183− (table) => [
184− uniqueIndex("source_recipe_documents_revision_kind_key_idx").on(
185− table.recipeRevisionId,
186− table.documentKind,
187− table.documentKey,
188− ),
189− index("source_recipe_documents_revision_created_idx").on(
190− table.recipeRevisionId,
191− table.createdAt,
192− table.id,
193− ),
194− ],
195−);
196−
197−export const sourceRecipeSchemaBundlesTable = pgTable(
198− tableNames.sourceRecipeSchemaBundles,
199− {
200− id: text("id").notNull().primaryKey(),
201− recipeRevisionId: text("recipe_revision_id").notNull(),
202− bundleKind: text("bundle_kind").notNull(),
203− refsJson: text("refs_json").notNull(),
204− contentHash: text("content_hash").notNull(),
205− createdAt: bigint("created_at", { mode: "number" }).notNull(),
206− updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
207− },
208− (table) => [
209− uniqueIndex("source_recipe_schema_bundles_revision_kind_idx").on(
210− table.recipeRevisionId,
211− table.bundleKind,
212− ),
213− index("source_recipe_schema_bundles_revision_created_idx").on(
214− table.recipeRevisionId,
215− table.createdAt,
216− table.id,
217− ),
218− ],
219−);
220−
221−export const sourceRecipeOperationsTable = pgTable(
222− tableNames.sourceRecipeOperations,
223− {
224− id: text("id").notNull().primaryKey(),
225− recipeRevisionId: text("recipe_revision_id").notNull(),
226− operationKey: text("operation_key").notNull(),
227− transportKind: text("transport_kind").notNull(),
228− toolId: text("tool_id").notNull(),
229− title: text("title"),
230− description: text("description"),
231− operationKind: text("operation_kind").notNull(),
232− searchText: text("search_text").notNull(),
233− inputSchemaJson: text("input_schema_json"),
234− outputSchemaJson: text("output_schema_json"),
235− providerKind: text("provider_kind").notNull(),
236− providerDataJson: text("provider_data_json"),
237− createdAt: bigint("created_at", { mode: "number" }).notNull(),
238− updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
239− },
240− (table) => [
241− uniqueIndex("source_recipe_operations_revision_operation_key_idx").on(
242− table.recipeRevisionId,
243− table.operationKey,
244− ),
245− index("source_recipe_operations_revision_tool_idx").on(
246− table.recipeRevisionId,
247− table.toolId,
248− table.updatedAt,
249− table.id,
250− ),
251− index("source_recipe_operations_search_text_idx").using(
252− "gin",
253− sql`to_tsvector('simple', ${table.searchText})`,
254− ),
255− check(
256− "source_recipe_operations_transport_kind_check",
257− sql`${table.transportKind} in ('http', 'graphql', 'mcp', 'internal')`,
258− ),
259− check(
260− "source_recipe_operations_kind_check",
261− sql`${table.operationKind} in ('read', 'write', 'delete', 'unknown')`,
262− ),
263− ],
264−);
265−
266−export const codeMigrationsTable = pgTable(
267− tableNames.codeMigrations,
268− {
269− id: text("id").notNull().primaryKey(),
270− appliedAt: bigint("applied_at", { mode: "number" }).notNull(),
271− },
272−);
273−
274−export const authArtifactsTable = pgTable(
275− tableNames.authArtifacts,
276− {
277− id: text("id").notNull().primaryKey(),
278− workspaceId: text("workspace_id").notNull(),
279− sourceId: text("source_id").notNull(),
280− actorAccountId: text("actor_account_id"),
281− slot: text("slot").notNull(),
282− artifactKind: text("artifact_kind").notNull(),
283− configJson: text("config_json").notNull(),
284− grantSetJson: text("grant_set_json"),
285− createdAt: bigint("created_at", { mode: "number" }).notNull(),
286− updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
287− },
288− (table) => [
289− index("auth_artifacts_workspace_idx").on(
290− table.workspaceId,
291− table.updatedAt,
292− table.id,
293− ),
294− uniqueIndex("auth_artifacts_workspace_source_actor_idx").on(
295− table.workspaceId,
296− table.sourceId,
297− table.actorAccountId,
298− table.slot,
299− ),
300− index("auth_artifacts_workspace_source_idx").on(
301− table.workspaceId,
302− table.sourceId,
303− table.updatedAt,
304− table.id,
305− ),
306− check(
307− "auth_artifacts_slot_check",
308− sql`${table.slot} in ('runtime', 'import')`,
309− ),
310− ],
311−);
312−
313−export const authLeasesTable = pgTable(
314− tableNames.authLeases,
315− {
316− id: text("id").notNull().primaryKey(),
317− authArtifactId: text("auth_artifact_id").notNull(),
318− workspaceId: text("workspace_id").notNull(),
319− sourceId: text("source_id").notNull(),
320− actorAccountId: text("actor_account_id"),
321− slot: text("slot").notNull(),
322− placementsTemplateJson: text("placements_template_json").notNull(),
323− expiresAt: bigint("expires_at", { mode: "number" }),
324− refreshAfter: bigint("refresh_after", { mode: "number" }),
325− createdAt: bigint("created_at", { mode: "number" }).notNull(),
326− updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
327− },
328− (table) => [
329− uniqueIndex("auth_leases_auth_artifact_idx").on(table.authArtifactId),
330− index("auth_leases_workspace_source_idx").on(
331− table.workspaceId,
332− table.sourceId,
333− table.actorAccountId,
334− table.slot,
335− table.updatedAt,
336− table.id,
337− ),
338− check(
339− "auth_leases_slot_check",
340− sql`${table.slot} in ('runtime', 'import')`,
341− ),
342− ],
343−);
344−
345−export const workspaceSourceOauthClientsTable = pgTable(
346− tableNames.workspaceSourceOauthClients,
347− {
348− id: text("id").notNull().primaryKey(),
349− workspaceId: text("workspace_id").notNull(),
350− sourceId: text("source_id").notNull(),
351− providerKey: text("provider_key").notNull(),
352− clientId: text("client_id").notNull(),
353− clientSecretProviderId: text("client_secret_provider_id"),
354− clientSecretHandle: text("client_secret_handle"),
355− clientMetadataJson: text("client_metadata_json"),
356− createdAt: bigint("created_at", { mode: "number" }).notNull(),
357− updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
358− },
359− (table) => [
360− uniqueIndex("workspace_source_oauth_clients_workspace_source_provider_idx").on(
361− table.workspaceId,
362− table.sourceId,
363− table.providerKey,
364− ),
365− index("workspace_source_oauth_clients_workspace_idx").on(
366− table.workspaceId,
367− table.updatedAt,
368− table.sourceId,
369− ),
370− ],
371−);
372−
373−export const secretMaterialsTable = pgTable(
374− tableNames.secretMaterials,
375− {
376− id: text("id").notNull().primaryKey(),
377− name: text("name"),
378− purpose: text("purpose").notNull(),
379− value: text("value").notNull(),
380− createdAt: bigint("created_at", { mode: "number" }).notNull(),
381− updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
382− },
383− (table) => [
384− index("secret_materials_updated_idx").on(table.updatedAt, table.id),
385− check(
386− "secret_materials_purpose_check",
387− sql`${table.purpose} in ('auth_material', 'oauth_access_token', 'oauth_refresh_token', 'oauth_client_info')`,
388− ),
389− ],
390−);
391−
392−export const sourceAuthSessionsTable = pgTable(
393− tableNames.sourceAuthSessions,
394− {
395− id: text("id").notNull().primaryKey(),
396− workspaceId: text("workspace_id").notNull(),
397− sourceId: text("source_id").notNull(),
398− actorAccountId: text("actor_account_id"),
399− credentialSlot: text("credential_slot").notNull(),
400− executionId: text("execution_id"),
401− interactionId: text("interaction_id"),
402− providerKind: text("provider_kind").notNull(),
403− status: text("status").notNull(),
404− state: text("state").notNull(),
405− sessionDataJson: text("session_data_json").notNull(),
406− errorText: text("error_text"),
407− completedAt: bigint("completed_at", { mode: "number" }),
408− createdAt: bigint("created_at", { mode: "number" }).notNull(),
409− updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
410− },
411− (table) => [
412− index("source_auth_sessions_workspace_idx").on(
413− table.workspaceId,
414− table.updatedAt,
415− table.id,
416− ),
417− index("source_auth_sessions_pending_idx").on(
418− table.workspaceId,
419− table.sourceId,
420− table.actorAccountId,
421− table.credentialSlot,
422− table.status,
423− table.updatedAt,
424− table.id,
425− ),
426− uniqueIndex("source_auth_sessions_state_idx").on(table.state),
427− check(
428− "source_auth_sessions_provider_kind_check",
429− sql`${table.providerKind} in ('mcp_oauth', 'oauth2_pkce')`,
430− ),
431− check(
432− "source_auth_sessions_status_check",
433− sql`${table.status} in ('pending', 'completed', 'failed', 'cancelled')`,
434− ),
435− check(
436− "source_auth_sessions_credential_slot_check",
437− sql`${table.credentialSlot} in ('runtime', 'import')`,
438− ),
439− ],
440−);
441−
442−export const executionsTable = pgTable(
443− tableNames.executions,
444− {
445− id: text("id").notNull().primaryKey(),
446− workspaceId: text("workspace_id").notNull(),
447− createdByAccountId: text("created_by_account_id").notNull(),
448− status: text("status").notNull(),
449− code: text("code").notNull(),
450− resultJson: text("result_json"),
451− errorText: text("error_text"),
452− logsJson: text("logs_json"),
453− startedAt: bigint("started_at", { mode: "number" }),
454− completedAt: bigint("completed_at", { mode: "number" }),
455− createdAt: bigint("created_at", { mode: "number" }).notNull(),
456− updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
457− },
458− (table) => [
459− index("executions_workspace_idx").on(table.workspaceId, table.updatedAt, table.id),
460− check(
461− "executions_status_check",
462− sql`${table.status} in ('pending', 'running', 'waiting_for_interaction', 'completed', 'failed', 'cancelled')`,
463− ),
464− ],
465−);
466−
467−export const executionInteractionsTable = pgTable(
468− tableNames.executionInteractions,
469− {
470− id: text("id").notNull().primaryKey(),
471− executionId: text("execution_id").notNull(),
472− status: text("status").notNull(),
473− kind: text("kind").notNull(),
474− purpose: text("purpose").notNull(),
475− payloadJson: text("payload_json").notNull(),
476− responseJson: text("response_json"),
477− responsePrivateJson: text("response_private_json"),
478− createdAt: bigint("created_at", { mode: "number" }).notNull(),
479− updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
480− },
481− (table) => [
482− index("execution_interactions_execution_idx").on(
483− table.executionId,
484− table.updatedAt,
485− table.id,
486− ),
487− check(
488− "execution_interactions_status_check",
489− sql`${table.status} in ('pending', 'resolved', 'cancelled')`,
490− ),
491− ],
492−);
493−
494−export const executionStepsTable = pgTable(
495− tableNames.executionSteps,
496− {
497− id: text("id").notNull().primaryKey(),
498− executionId: text("execution_id").notNull(),
499− sequence: bigint("sequence", { mode: "number" }).notNull(),
500− kind: text("kind").notNull(),
501− status: text("status").notNull(),
502− path: text("path").notNull(),
503− argsJson: text("args_json").notNull(),
504− resultJson: text("result_json"),
505− errorText: text("error_text"),
506− interactionId: text("interaction_id"),
507− createdAt: bigint("created_at", { mode: "number" }).notNull(),
508− updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
509− },
510− (table) => [
511− uniqueIndex("execution_steps_execution_sequence_idx").on(
512− table.executionId,
513− table.sequence,
514− ),
515− index("execution_steps_execution_updated_idx").on(
516− table.executionId,
517− table.updatedAt,
518− table.id,
519− ),
520− check(
521− "execution_steps_kind_check",
522− sql`${table.kind} in ('tool_call')`,
523− ),
524− check(
525− "execution_steps_status_check",
526− sql`${table.status} in ('pending', 'waiting', 'completed', 'failed')`,
527− ),
528− ],
529−);
530−
531−export const drizzleSchema = {
532− accountsTable,
533− sourcesTable,
534− sourceRecipesTable,
535− sourceRecipeRevisionsTable,
536− sourceRecipeDocumentsTable,
537− sourceRecipeSchemaBundlesTable,
538− sourceRecipeOperationsTable,
539− codeMigrationsTable,
540− authArtifactsTable,
541− authLeasesTable,
542− workspaceSourceOauthClientsTable,
543− secretMaterialsTable,
544− sourceAuthSessionsTable,
545− executionsTable,
546− executionInteractionsTable,
547− executionStepsTable,
548−};
549−
550−export type DrizzleTables = typeof drizzleSchema;
packages/control-plane/src/persistence/sql-runtime.ts+0−206
@@ -1,206 +0,0 @@
1−import { PGlite } from "@electric-sql/pglite";
2−import { drizzle as drizzlePGlite } from "drizzle-orm/pglite";
3−import { migrate as migratePGlite } from "drizzle-orm/pglite/migrator";
4−import { drizzle as drizzlePostgres } from "drizzle-orm/postgres-js";
5−import { migrate as migratePostgres } from "drizzle-orm/postgres-js/migrator";
6−import { existsSync, readdirSync, readFileSync } from "node:fs";
7−import { mkdir, unlink } from "node:fs/promises";
8−import * as path from "node:path";
9−import { fileURLToPath } from "node:url";
10−import postgres from "postgres";
11−
12−import { drizzleSchema } from "./schema";
13−
14−export type SqlBackend = "pglite" | "postgres";
15−
16−export type CreateSqlRuntimeOptions = {
17− databaseUrl?: string;
18− localDataDir?: string;
19− postgresApplicationName?: string;
20− migrationsFolder?: string;
21−};
22−
23−const createPGliteDb = (client: PGlite) =>
24− drizzlePGlite({ client, schema: drizzleSchema });
25−
26−const createPostgresDb = (client: postgres.Sql) =>
27− drizzlePostgres({ client, schema: drizzleSchema });
28−
29−export type PGliteDb = ReturnType<typeof createPGliteDb>;
30−export type PostgresDb = ReturnType<typeof createPostgresDb>;
31−export type DrizzleDb = PGliteDb | PostgresDb;
32−
33−export type SqlRuntime = {
34− backend: SqlBackend;
35− db: DrizzleDb;
36− close: () => Promise<void>;
37−};
38−
39−const trim = (value: string | undefined): string | undefined => {
40− const candidate = value?.trim();
41− return candidate && candidate.length > 0 ? candidate : undefined;
42−};
43−
44−const isPostgresUrl = (value: string): boolean =>
45− value.startsWith("postgres://") || value.startsWith("postgresql://");
46−
47−/**
48− * Remove a stale PGlite `postmaster.pid` lock file if present.
49− *
50− * PGlite writes this file on open and removes it on close. When a process
51− * exits without calling `PGlite.close()` (e.g. the Vite dev server is
52− * killed with SIGKILL, or the terminal is closed), the lock file is left
53− * behind and prevents subsequent PGlite instances from opening the
54− * database. PGlite uses a synthetic PID (e.g. `-42`) that is never a real
55− * OS process, so we cannot use it to detect a live owner. Instead we rely
56− * on the fact that in the local single-user context only one process
57− * should ever own this database at a time.
58− */
59−const cleanupStalePGliteLock = async (dataDir: string): Promise<void> => {
60− const lockPath = path.join(dataDir, "postmaster.pid");
61− if (!existsSync(lockPath)) {
62− return;
63− }
64−
65− // PGlite's synthetic PID is always negative (e.g. -42). A real Postgres
66− // postmaster would write a positive PID. If we ever see a positive PID
67− // we leave the file alone.
68− try {
69− const content = readFileSync(lockPath, "utf-8");
70− const firstLine = content.split("\n")[0]?.trim();
71− const pid = Number(firstLine);
72− if (!Number.isNaN(pid) && pid > 0) {
73− // Looks like a real Postgres PID — don't touch it.
74− return;
75− }
76− } catch {
77− // If we can't read the file, try to remove it anyway.
78− }
79−
80− try {
81− await unlink(lockPath);
82− } catch {
83− // Best-effort cleanup; PGlite will report the real error if this fails.
84− }
85−};
86−
87−const createPGliteRuntime = async (localDataDir: string): Promise<SqlRuntime> => {
88− const normalized = trim(localDataDir) ?? ".executor/control-plane-pgdata";
89−
90− let client: PGlite;
91− if (normalized === ":memory:") {
92− client = new PGlite();
93− } else {
94− const resolvedDataDir = path.resolve(normalized);
95− if (!existsSync(resolvedDataDir)) {
96− await mkdir(resolvedDataDir, { recursive: true });
97− }
98− await cleanupStalePGliteLock(resolvedDataDir);
99− client = new PGlite(resolvedDataDir);
100− }
101−
102− const db = createPGliteDb(client);
103−
104− return {
105− backend: "pglite",
106− db,
107− close: async () => {
108− await client.close();
109− },
110− };
111−};
112−
113−const createPostgresRuntime = async (
114− databaseUrl: string,
115− applicationName: string | undefined,
116−): Promise<SqlRuntime> => {
117− const client = postgres(databaseUrl, {
118− prepare: false,
119− max: 10,
120− ...(applicationName
121− ? { connection: { application_name: applicationName } }
122− : {}),
123− });
124− const db = createPostgresDb(client);
125−
126− return {
127− backend: "postgres",
128− db,
129− close: async () => {
130− await client.end({ timeout: 5 });
131− },
132− };
133−};
134−
135−const resolveMigrationsFolder = (explicit: string | undefined): string => {
136− const explicitTrimmed = trim(explicit);
137− if (explicitTrimmed) {
138− return path.resolve(explicitTrimmed);
139− }
140−
141− const moduleDir = path.dirname(fileURLToPath(import.meta.url));
142− const packageRoot = path.resolve(moduleDir, "../..");
143− const cwd = process.cwd();
144− const candidates = [
145− path.resolve(packageRoot, "drizzle"),
146− path.resolve(cwd, "packages/control-plane/drizzle"),
147− path.resolve(cwd, "drizzle"),
148− ];
149−
150− const hasModernMigrations = (candidate: string): boolean => {
151− if (!existsSync(candidate)) {
152− return false;
153− }
154−
155− const entries = readdirSync(candidate, { withFileTypes: true });
156− return entries.some(
157− (entry) =>
158− entry.isDirectory()
159− && existsSync(path.join(candidate, entry.name, "migration.sql")),
160− );
161− };
162−
163− for (const candidate of candidates) {
164− if (
165− existsSync(path.join(candidate, "meta", "_journal.json"))
166− || hasModernMigrations(candidate)
167− ) {
168− return candidate;
169− }
170− }
171−
172− throw new Error(
173− "Unable to resolve Drizzle migrations folder for control-plane persistence",
174− );
175−};
176−
177−export const runMigrations = async (
178− runtime: SqlRuntime,
179− options?: { migrationsFolder?: string },
180−): Promise<void> => {
181− const migrationsFolder = resolveMigrationsFolder(options?.migrationsFolder);
182−
183− if (runtime.backend === "pglite") {
184− await migratePGlite(runtime.db as PGliteDb, { migrationsFolder });
185− return;
186− }
187−
188− await migratePostgres(runtime.db as PostgresDb, { migrationsFolder });
189−};
190−
191−export const createSqlRuntime = async (
192− options: CreateSqlRuntimeOptions,
193−): Promise<SqlRuntime> => {
194− const databaseUrl = trim(options.databaseUrl);
195− const runtime =
196− databaseUrl && isPostgresUrl(databaseUrl)
197− ? await createPostgresRuntime(
198− databaseUrl,
199− trim(options.postgresApplicationName),
200− )
201− : await createPGliteRuntime(
202− options.localDataDir ?? ".executor/control-plane-pgdata",
203− );
204−
205− return runtime;
206−};
packages/control-plane/src/runtime/auth-leases.ts+6−6
@@ -1,4 +1,3 @@
1−import type { SqlControlPlaneRows } from "#persistence";
21 import type {
32 AuthArtifact,
43 AuthLease,
@@ -14,6 +13,7 @@ import { · 9 unmodified lines
1413 import * as Effect from "effect/Effect";
1514 import * as Option from "effect/Option";
1615
16+import type { ControlPlaneStoreShape } from "./store";
1717 import { resolveAuthArtifactMaterial, type ResolvedSourceAuthMaterial } from "./auth-artifacts";
1818 import {
1919 createDefaultSecretMaterialDeleter,
@@ -33,7 +33,7 @@ const secretRefKey = (ref: SecretRef): string => · 13 unmodified lines
3333 `${ref.providerId}:${ref.handle}`;
3434
3535 const cleanupAuthLeaseSecretRefs = (
36− rows: SqlControlPlaneRows,
36+ rows: ControlPlaneStoreShape,
3737 input: {
3838 previous: Pick<AuthLease, "placementsTemplateJson"> | null;
3939 next: Pick<AuthLease, "placementsTemplateJson"> | null;
@@ -76,7 +76,7 @@ const leaseIsFresh = (lease: AuthLease | null, now: number): boolean => { · 36 unmodified lines
7676 };
7777
7878 const refreshRefreshableOauth2AuthorizedUserArtifact = (input: {
79− rows: SqlControlPlaneRows;
79+ rows: ControlPlaneStoreShape;
8080 artifact: AuthArtifact;
8181 lease: AuthLease | null;
8282 resolveSecretMaterial: ResolveSecretMaterial;
@@ -168,7 +168,7 @@ const refreshRefreshableOauth2AuthorizedUserArtifact = (input: { · 85 unmodified lines
168168 return nextLease;
169169 });
170170
171−export const removeAuthLeaseAndSecrets = (rows: SqlControlPlaneRows, input: {
171+export const removeAuthLeaseAndSecrets = (rows: ControlPlaneStoreShape, input: {
172172 authArtifactId: AuthArtifact["id"];
173173 }): Effect.Effect<void, Error, never> =>
174174 Effect.gen(function* () {
@@ -185,7 +185,7 @@ export const removeAuthLeaseAndSecrets = (rows: SqlControlPlaneRows, input: { · 10 unmodified lines
185185 });
186186
187187 export const upsertOauth2AuthorizedUserLeaseFromTokenResponse = (input: {
188− rows: SqlControlPlaneRows;
188+ rows: ControlPlaneStoreShape;
189189 artifact: AuthArtifact;
190190 tokenResponse: OAuth2TokenResponse;
191191 }): Effect.Effect<void, Error, never> =>
@@ -256,7 +256,7 @@ export const upsertOauth2AuthorizedUserLeaseFromTokenResponse = (input: { · 64 unmodified lines
256256 });
257257
258258 export const resolveAuthArtifactMaterialWithLeases = (input: {
259− rows: SqlControlPlaneRows;
259+ rows: ControlPlaneStoreShape;
260260 artifact: AuthArtifact | null;
261261 resolveSecretMaterial: ResolveSecretMaterial;
262262 context?: SecretMaterialResolveContext;
packages/control-plane/src/runtime/control-plane-runtime.test.ts+1−1
@@ -319,7 +319,7 @@ describe("control-plane-runtime", () => { · 318 unmodified lines
319319 decodedCredentialSelection.authKind === "bearer"
320320 ? decodedCredentialSelection.tokenRef.providerId
321321 : null,
322− ).toBe("postgres");
322+ ).toBe("local");
323323
324324 const tokenSecretMaterialId = SecretMaterialIdSchema.make(
325325 decodedCredentialSelection.authKind === "bearer"
packages/control-plane/src/runtime/execution-replay.test.ts+0−271
@@ -1,271 +0,0 @@
1−import { describe, expect, it } from "@effect/vitest";
2−import * as Effect from "effect/Effect";
3−import * as Schema from "effect/Schema";
4−
5−import { makeToolInvokerFromTools } from "@executor/codemode-core";
6−import { makeDenoSubprocessExecutor } from "@executor/runtime-deno-subprocess";
7−
8−import {
9− createSqlControlPlaneRuntime,
10− type ResolveExecutionEnvironment,
11−} from "./index";
12−import { withControlPlaneClient } from "./test-http-client";
13−
14−const gatedEchoElicitation = {
15− message: "Approve gated echo?",
16− requestedSchema: {
17− type: "object",
18− properties: {
19− approve: {
20− type: "boolean",
21− title: "Approve",
22− },
23− },
24− required: ["approve"],
25− },
26−} as const;
27−
28−const makeReplayResolver = (countedCalls: { value: number }): ResolveExecutionEnvironment =>
29− ({ onElicitation }) =>
30− Effect.succeed({
31− executor: makeDenoSubprocessExecutor(),
32− toolInvoker: makeToolInvokerFromTools({
33− onElicitation,
34− tools: {
35− "counted.add": {
36− description: "Adds numbers and counts calls",
37− inputSchema: Schema.standardSchemaV1(
38− Schema.Struct({
39− a: Schema.optional(Schema.Number),
40− b: Schema.optional(Schema.Number),
41− }),
42− ),
43− execute: ({ a, b }: { a?: number; b?: number }) => {
44− countedCalls.value += 1;
45− return { sum: (a ?? 0) + (b ?? 0) };
46− },
47− },
48− "gated.echo": {
49− tool: {
50− description: "Echoes only after approval",
51− inputSchema: Schema.standardSchemaV1(
52− Schema.Struct({
53− value: Schema.String,
54− approve: Schema.optional(Schema.Boolean),
55− }),
56− ),
57− execute: ({ value, approve }: { value: string; approve?: boolean }) => ({
58− text: approve === true ? `approved:${value}` : `denied:${value}`,
59− }),
60− },
61− metadata: {
62− interaction: "required",
63− elicitation: gatedEchoElicitation,
64− },
65− },
66− },
67− }),
68− });
69−
70−const createRuntime = (countedCalls: { value: number }) =>
71− Effect.acquireRelease(
72− createSqlControlPlaneRuntime({
73− localDataDir: ":memory:",
74− executionResolver: makeReplayResolver(countedCalls),
75− }),
76− (runtime) => Effect.promise(() => runtime.close()).pipe(Effect.orDie),
77− );
78−
79−describe("execution replay", () => {
80− it.scoped("replays completed proxy calls instead of re-executing them", () =>
81− Effect.gen(function* () {
82− const countedCalls = { value: 0 };
83− const runtime = yield* createRuntime(countedCalls);
84− const installation = runtime.localInstallation;
85−
86− const created = yield* withControlPlaneClient(
87− {
88− runtime,
89− accountId: installation.accountId,
90− },
91− (client) =>
92− client.executions.create({
93− path: {
94− workspaceId: installation.workspaceId,
95− },
96− payload: {
97− code: [
98− "const sum = await tools.counted.add({ a: 20, b: 22 });",
99− 'const echo = await tools.gated.echo({ value: "from-replay" });',
100− "return { sum, echo };",
101− ].join("\n"),
102− },
103− }),
104− );
105−
106− expect(created.execution.status).toBe("waiting_for_interaction");
107− expect(countedCalls.value).toBe(1);
108−
109− const resumed = yield* withControlPlaneClient(
110− {
111− runtime,
112− accountId: installation.accountId,
113− },
114− (client) =>
115− client.executions.resume({
116− path: {
117− workspaceId: installation.workspaceId,
118− executionId: created.execution.id,
119− },
120− payload: {
121− responseJson: JSON.stringify({
122− action: "accept",
123− content: {
124− approve: true,
125− },
126− }),
127− },
128− }),
129− );
130−
131− expect(resumed.execution.status).toBe("completed");
132− expect(countedCalls.value).toBe(1);
133− expect(resumed.execution.resultJson).toBe(
134− JSON.stringify({
135− sum: { sum: 42 },
136− echo: { text: "approved:from-replay" },
137− }),
138− );
139−
140− const completedSteps = yield* runtime.persistence.rows.executionSteps.listByExecutionId(
141− created.execution.id,
142− );
143− expect(completedSteps).toHaveLength(0);
144− }),
145− );
146−
147− it.scoped("keeps live form interactions in the same run when requested", () =>
148− Effect.gen(function* () {
149− const countedCalls = { value: 0 };
150− const runtime = yield* createRuntime(countedCalls);
151− const installation = runtime.localInstallation;
152−
153− const created = yield* withControlPlaneClient(
154− {
155− runtime,
156− accountId: installation.accountId,
157− },
158− (client) =>
159− client.executions.create({
160− path: {
161− workspaceId: installation.workspaceId,
162− },
163− payload: {
164− interactionMode: "live_form",
165− code: [
166− "const nonce = String(Math.random());",
167− "return await tools.gated.echo({ value: nonce });",
168− ].join("\n"),
169− },
170− }),
171− );
172−
173− expect(created.execution.status).toBe("waiting_for_interaction");
174−
175− const resumed = yield* withControlPlaneClient(
176− {
177− runtime,
178− accountId: installation.accountId,
179− },
180− (client) =>
181− client.executions.resume({
182− path: {
183− workspaceId: installation.workspaceId,
184− executionId: created.execution.id,
185− },
186− payload: {
187− interactionMode: "live_form",
188− responseJson: JSON.stringify({
189− action: "accept",
190− content: {
191− approve: true,
192− },
193− }),
194− },
195− }),
196− );
197−
198− expect(resumed.execution.status).toBe("completed");
199− expect(resumed.execution.resultJson).toContain("approved:");
200− }),
201− );
202−
203− it.scoped("fails loudly when replayed code reaches a different proxy call", () =>
204− Effect.gen(function* () {
205− const countedCalls = { value: 0 };
206− const runtime = yield* createRuntime(countedCalls);
207− const installation = runtime.localInstallation;
208−
209− const created = yield* withControlPlaneClient(
210− {
211− runtime,
212− accountId: installation.accountId,
213− },
214− (client) =>
215− client.executions.create({
216− path: {
217− workspaceId: installation.workspaceId,
218− },
219− payload: {
220− code: [
221− "await tools.counted.add({ a: 1, b: 2 });",
222− 'return await tools.gated.echo({ value: "mismatch" });',
223− ].join("\n"),
224− },
225− }),
226− );
227−
228− expect(created.execution.status).toBe("waiting_for_interaction");
229− expect(countedCalls.value).toBe(1);
230−
231− yield* runtime.persistence.rows.executions.update(created.execution.id, {
232− code: [
233− "await tools.counted.add({ a: 1, b: 999 });",
234− 'return await tools.gated.echo({ value: "mismatch" });',
235− ].join("\n"),
236− updatedAt: Date.now(),
237− });
238−
239− const resumed = yield* withControlPlaneClient(
240− {
241− runtime,
242− accountId: installation.accountId,
243− },
244− (client) =>
245− client.executions.resume({
246− path: {
247− workspaceId: installation.workspaceId,
248− executionId: created.execution.id,
249− },
250− payload: {
251− responseJson: JSON.stringify({
252− action: "accept",
253− content: {
254− approve: true,
255− },
256− }),
257− },
258− }),
259− );
260−
261− expect(resumed.execution.status).toBe("failed");
262− expect(resumed.execution.errorText).toContain("Durable execution mismatch");
263− expect(countedCalls.value).toBe(1);
264−
265− const failedSteps = yield* runtime.persistence.rows.executionSteps.listByExecutionId(
266− created.execution.id,
267− );
268− expect(failedSteps).toHaveLength(0);
269− }),
270− );
271−});
packages/control-plane/src/runtime/executor-tools.ts+21−13
@@ -18,25 +18,33 @@ import { · 17 unmodified lines
1818 import * as Effect from "effect/Effect";
1919 import * as Cause from "effect/Cause";
2020 import * as Exit from "effect/Exit";
21+import * as Layer from "effect/Layer";
2122 import * as Schema from "effect/Schema";
2223 import type { RuntimeLocalWorkspaceState } from "./local-runtime-context";
23−import { RuntimeLocalWorkspaceService } from "./local-runtime-context";
24+import {
25+ InstallationStore,
26+ type LocalStorageServices,
27+ LocalInstallationStore,
28+ LocalSourceArtifactStore,
29+ LocalWorkspaceConfigStore,
30+ LocalWorkspaceStateStore,
31+ SourceArtifactStore,
32+ WorkspaceConfigStore,
33+ WorkspaceStateStore,
34+} from "./local-storage";
2435
2536 /** Run an Effect as a Promise, preserving the original error (not FiberFailure). */
2637 const runEffect = async <A>(
27− effect: Effect.Effect<A, unknown, never>,
28− runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null = null,
38+ effect: Effect.Effect<A, unknown, LocalStorageServices>,
39+ _runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null = null,
2940 ): Promise<A> => {
30− const provided =
31− runtimeLocalWorkspace === null
32− ? effect
33− : effect.pipe(
34− Effect.provideService(
35− RuntimeLocalWorkspaceService,
36− runtimeLocalWorkspace,
37− ),
38− );
39− const exit = await Effect.runPromiseExit(provided);
41+ const baseLayer = Layer.mergeAll(
42+ Layer.succeed(InstallationStore, LocalInstallationStore),
43+ Layer.succeed(WorkspaceConfigStore, LocalWorkspaceConfigStore),
44+ Layer.succeed(WorkspaceStateStore, LocalWorkspaceStateStore),
45+ Layer.succeed(SourceArtifactStore, LocalSourceArtifactStore),
46+ );
47+ const exit = await Effect.runPromiseExit(effect.pipe(Effect.provide(baseLayer)));
4048 if (Exit.isSuccess(exit)) return exit.value;
4149 throw Cause.squash(exit.cause);
4250 };
packages/control-plane/src/runtime/index.ts+90−102
@@ -1,19 +1,9 @@
1−import * as Context from "effect/Context";
21 import * as Effect from "effect/Effect";
32 import * as Exit from "effect/Exit";
43 import * as Layer from "effect/Layer";
54 import * as Scope from "effect/Scope";
65
76 import { type ControlPlaneApiRuntimeContext } from "#api";
8−import {
9− SqlControlPlanePersistenceLive,
10− SqlControlPlanePersistenceService,
11− SqlControlPlaneRowsLive,
12− SqlControlPlaneRowsService,
13− SqlPersistenceBootstrapError,
14− type CreateSqlRuntimeOptions,
15− type SqlControlPlanePersistence,
16−} from "#persistence";
177 import type { LocalInstallation } from "#schema";
188
199 import { type ResolveExecutionEnvironment } from "./execution-state";
@@ -22,14 +12,33 @@ import { · 2 unmodified lines
2212 LiveExecutionManagerLive,
2313 LiveExecutionManagerService,
2414 } from "./live-execution";
25−import { getOrProvisionLocalInstallation } from "./local-installation";
2615 import {
27− loadLocalExecutorConfig,
16+ createLocalControlPlanePersistence,
17+ type LocalControlPlanePersistence,
18+} from "./local-control-plane-store";
19+import {
2820 resolveLocalWorkspaceContext,
2921 } from "./local-config";
30−import { RuntimeLocalWorkspaceService } from "./local-runtime-context";
22+import {
23+ InstallationStore,
24+ LocalInstallationStore,
25+ LocalInstallationStoreLive,
26+ LocalSourceArtifactStore,
27+ LocalSourceArtifactStoreLive,
28+ SourceArtifactStore,
29+ LocalWorkspaceConfigStore,
30+ LocalWorkspaceConfigStoreLive,
31+ LocalWorkspaceStateStore,
32+ LocalWorkspaceStateStoreLive,
33+ WorkspaceConfigStore,
34+ WorkspaceStateStore,
35+} from "./local-storage";
36+import {
37+ type RuntimeLocalWorkspaceState,
38+ RuntimeLocalWorkspaceService,
39+} from "./local-runtime-context";
3140 import { synchronizeLocalWorkspaceState } from "./local-workspace-sync";
32−import { ControlPlaneStore, ControlPlaneStoreLive } from "./store";
41+import { ControlPlaneStore, type ControlPlaneStoreShape } from "./store";
3342 import {
3443 createRuntimeSourceAuthService,
3544 RuntimeSourceAuthServiceLive,
@@ -48,6 +57,7 @@ export * from "./executor-tools"; · 12 unmodified lines
4857 export * from "./live-execution";
4958 export * from "./local-config";
5059 export * from "./local-installation";
60+export * from "./local-storage";
5161 export * from "./local-source-artifacts";
5262 export * from "./local-tools";
5363 export * from "./schema-type-signature";
@@ -73,12 +83,9 @@ const detailsFromCause = (cause: unknown): string => · 19 unmodified lines
7383
7484 const toLocalRuntimeBootstrapError = (
7585 cause: unknown,
76−): SqlPersistenceBootstrapError => {
86+): Error => {
7787 const details = detailsFromCause(cause);
78− return new SqlPersistenceBootstrapError({
79− message: `Failed initializing local runtime: ${details}`,
80− details,
81− });
88+ return new Error(`Failed initializing local runtime: ${details}`);
8289 };
8390
8491 const closeScope = (scope: Scope.CloseableScope) =>
@@ -90,88 +97,57 @@ export type RuntimeControlPlaneLayer = Layer.Layer< · 5 unmodified lines
9097 never
9198 >;
9299
93−const runtimeContextTags = [
94− ControlPlaneStore,
95− LiveExecutionManagerService,
96− RuntimeLocalWorkspaceService,
97− RuntimeSourceAuthServiceTag,
98− RuntimeExecutionResolverService,
99−] as const;
100−
101−const createRuntimeLayerFromContext = (
102− context: Context.Context<
103− | ControlPlaneStore
104− | LiveExecutionManagerService
105− | RuntimeLocalWorkspaceService
106− | RuntimeSourceAuthServiceTag
107− | RuntimeExecutionResolverService
108− >,
109−): RuntimeControlPlaneLayer => {
110− const runtimeContext = context.pipe(
111− Context.pick(...runtimeContextTags),
112− ) as Context.Context<
113− | ControlPlaneStore
114− | LiveExecutionManagerService
115− | RuntimeLocalWorkspaceService
116− | RuntimeSourceAuthServiceTag
117− | RuntimeExecutionResolverService
118− >;
119−
120− return Layer.succeedContext(runtimeContext) as RuntimeControlPlaneLayer;
121−};
122−
123100 export const createRuntimeControlPlaneLayer = (
124− options: RuntimeControlPlaneOptions = {},
101+ input: RuntimeControlPlaneOptions & {
102+ store: ControlPlaneStoreShape;
103+ localWorkspaceState: RuntimeLocalWorkspaceState;
104+ },
125105 ) => {
126− const liveExecutionManagerLayer = LiveExecutionManagerLive;
106+ const baseLayer = Layer.mergeAll(
107+ Layer.succeed(ControlPlaneStore, input.store),
108+ Layer.succeed(RuntimeLocalWorkspaceService, input.localWorkspaceState),
109+ LocalInstallationStoreLive,
110+ LocalWorkspaceConfigStoreLive,
111+ LocalWorkspaceStateStoreLive,
112+ LocalSourceArtifactStoreLive,
113+ LiveExecutionManagerLive,
114+ );
115+
127116 const sourceAuthLayer = RuntimeSourceAuthServiceLive({
128− getLocalServerBaseUrl: options.getLocalServerBaseUrl,
129− }).pipe(Layer.provide(liveExecutionManagerLayer));
117+ getLocalServerBaseUrl: input.getLocalServerBaseUrl,
118+ localConfig: input.localWorkspaceState.loadedConfig.config,
119+ workspaceRoot: input.localWorkspaceState.context.workspaceRoot,
120+ localWorkspaceState: input.localWorkspaceState,
121+ }).pipe(Layer.provide(baseLayer));
122+
130123 const executionResolverLayer = RuntimeExecutionResolverLive({
131− executionResolver: options.executionResolver,
132− resolveSecretMaterial: options.resolveSecretMaterial,
133− }).pipe(Layer.provide(sourceAuthLayer));
124+ executionResolver: input.executionResolver,
125+ resolveSecretMaterial: input.resolveSecretMaterial,
126+ }).pipe(
127+ Layer.provide(Layer.mergeAll(baseLayer, sourceAuthLayer)),
128+ );
134129
135130 return Layer.mergeAll(
136− ControlPlaneStoreLive,
137− liveExecutionManagerLayer,
131+ baseLayer,
138132 sourceAuthLayer,
139133 executionResolverLayer,
140− );
134+ ) as RuntimeControlPlaneLayer;
141135 };
142136
143137 export type ControlPlaneRuntime = {
144− persistence: SqlControlPlanePersistence;
138+ persistence: LocalControlPlanePersistence;
145139 localInstallation: LocalInstallation;
146140 runtimeLayer: RuntimeControlPlaneLayer;
147141 close: () => Promise<void>;
148142 };
149143
150−export type CreateControlPlaneRuntimeOptions = CreateSqlRuntimeOptions &
151− RuntimeControlPlaneOptions;
144+export type CreateControlPlaneRuntimeOptions = RuntimeControlPlaneOptions;
152145
153146 export const createControlPlaneRuntime = (
154147 options: CreateControlPlaneRuntimeOptions,
155−): Effect.Effect<ControlPlaneRuntime, SqlPersistenceBootstrapError> =>
148+): Effect.Effect<ControlPlaneRuntime, Error> =>
156149 Effect.gen(function* () {
157150 const scope = yield* Scope.make();
158− const persistenceAndRowsLayer = SqlControlPlaneRowsLive.pipe(
159− Layer.provideMerge(SqlControlPlanePersistenceLive(options)),
160− );
161− const baseContext = yield* Layer.buildWithScope(
162− persistenceAndRowsLayer,
163− scope,
164− ).pipe(
165− Effect.catchAll((error) =>
166− closeScope(scope).pipe(Effect.zipRight(Effect.fail(error))),
167− ),
168− );
169−
170− const persistence = Context.get(
171− baseContext,
172− SqlControlPlanePersistenceService,
173− );
174− const rows = Context.get(baseContext, SqlControlPlaneRowsService);
175151
176152 const localWorkspaceContext = yield* resolveLocalWorkspaceContext({
177153 workspaceRoot: options.workspaceRoot,
@@ -182,7 +158,12 @@ export const createControlPlaneRuntime = ( · 4 unmodified lines
182158 ),
183159 );
184160
185− const localInstallation = yield* getOrProvisionLocalInstallation({
161+ const installationStore = LocalInstallationStore;
162+ const workspaceConfigStore = LocalWorkspaceConfigStore;
163+ const workspaceStateStore = LocalWorkspaceStateStore;
164+ const sourceArtifactStore = LocalSourceArtifactStore;
165+
166+ const localInstallation = yield* installationStore.getOrProvision({
186167 context: localWorkspaceContext,
187168 }).pipe(
188169 Effect.mapError(toLocalRuntimeBootstrapError),
@@ -191,7 +172,10 @@ export const createControlPlaneRuntime = ( · 2 unmodified lines
191172 ),
192173 );
193174
194− const loadedLocalConfig = yield* loadLocalExecutorConfig(
175+ const persistence = createLocalControlPlanePersistence(localWorkspaceContext);
176+ const rows = persistence.rows;
177+
178+ const loadedLocalConfig = yield* workspaceConfigStore.load(
195179 localWorkspaceContext,
196180 ).pipe(
197181 Effect.mapError(toLocalRuntimeBootstrapError),
@@ -237,38 +221,42 @@ export const createControlPlaneRuntime = ( · 39 unmodified lines
237221 },
238222 },
239223 });
224+
225+ const runtimeLocalWorkspaceState: RuntimeLocalWorkspaceState = {
226+ context: localWorkspaceContext,
227+ installation: {
228+ workspaceId: localInstallation.workspaceId,
229+ accountId: localInstallation.accountId,
230+ },
231+ loadedConfig: {
232+ ...loadedLocalConfig,
233+ config: effectiveLocalConfig,
234+ },
235+ };
236+
240237 const executionResolver =
241238 options.executionResolver ??
242239 createWorkspaceExecutionEnvironmentResolver({
243240 rows,
244241 sourceAuthService,
245242 resolveSecretMaterial,
243+ workspaceConfigStore,
244+ workspaceStateStore,
245+ sourceArtifactStore,
246246 });
247247
248− const runtimeContext = Context.empty().pipe(
249− Context.add(ControlPlaneStore, rows),
250− Context.add(LiveExecutionManagerService, liveExecutionManager),
251− Context.add(RuntimeLocalWorkspaceService, {
252− context: localWorkspaceContext,
253− installation: {
254− workspaceId: localInstallation.workspaceId,
255− accountId: localInstallation.accountId,
256− },
257− loadedConfig: {
258− ...loadedLocalConfig,
259− config: effectiveLocalConfig,
260− },
261− }),
262− Context.add(RuntimeSourceAuthServiceTag, sourceAuthService),
263− Context.add(RuntimeExecutionResolverService, executionResolver),
264− );
265− const concreteRuntimeLayer = createRuntimeLayerFromContext(runtimeContext);
248+ const concreteRuntimeLayer = createRuntimeControlPlaneLayer({
249+ ...options,
250+ store: rows,
251+ localWorkspaceState: runtimeLocalWorkspaceState,
252+ resolveSecretMaterial,
253+ executionResolver,
254+ });
266255
267256 return {
268257 persistence,
269258 localInstallation,
270259 runtimeLayer: concreteRuntimeLayer,
271− close: () =>
272− Effect.runPromise(Scope.close(scope, Exit.void)).catch(() => {}),
260+ close: () => Effect.runPromise(Scope.close(scope, Exit.void)).catch(() => {}),
273261 };
274262 });
packages/control-plane/src/runtime/live-execution.test.ts+1−1
@@ -9,7 +9,7 @@ describe("live-execution", () => { · 8 unmodified lines
99 content: {
1010 authKind: "bearer",
1111 tokenRef: {
12− providerId: "postgres",
12+ providerId: "local",
1313 handle: "sec_123",
1414 },
1515 },
packages/control-plane/src/runtime/live-execution.ts+3−2
@@ -2,7 +2,6 @@ import type { · 1 unmodified line
22 ElicitationResponse,
33 OnElicitation,
44 } from "@executor/codemode-core";
5−import { type SqlControlPlaneRows } from "#persistence";
65 import {
76 ExecutionInteractionIdSchema,
87 type Execution,
@@ -13,6 +12,8 @@ import * as Deferred from "effect/Deferred"; · 4 unmodified lines
1312 import * as Effect from "effect/Effect";
1413 import * as Layer from "effect/Layer";
1514
15+import type { ControlPlaneStoreShape } from "./store";
16+
1617 type VisibleExecutionState =
1718 | "waiting_for_interaction"
1819 | "completed"
@@ -35,7 +36,7 @@ type LiveExecutionManagerShape = { · 16 unmodified lines
3536 executionId: Execution["id"],
3637 ) => Effect.Effect<Deferred.Deferred<VisibleExecutionState>>;
3738 createOnElicitation: (input: {
38− rows: SqlControlPlaneRows;
39+ rows: ControlPlaneStoreShape;
3940 executionId: Execution["id"];
4041 }) => OnElicitation;
4142 resolveInteraction: (input: {
packages/control-plane/src/runtime/local-control-plane-store.ts+1054−0
@@ -0,0 +1,1054 @@
1+import { randomUUID } from "node:crypto";
2+import { existsSync } from "node:fs";
3+import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
4+import { dirname, join, resolve } from "node:path";
5+
6+import {
7+ AuthArtifactSchema,
8+ AuthLeaseSchema,
9+ type AuthArtifact,
10+ type AuthLease,
11+ type Execution,
12+ type ExecutionInteraction,
13+ type ExecutionStep,
14+ ExecutionInteractionSchema,
15+ ExecutionSchema,
16+ ExecutionStepSchema,
17+ SecretMaterialSchema,
18+ type SecretMaterial,
19+ type SourceAuthSession,
20+ SourceAuthSessionSchema,
21+ type WorkspaceSourceOauthClient,
22+ WorkspaceSourceOauthClientSchema,
23+} from "#schema";
24+import * as Effect from "effect/Effect";
25+import * as Option from "effect/Option";
26+import * as Schema from "effect/Schema";
27+
28+import { authArtifactSecretMaterialRefs } from "./auth-artifacts";
29+import type { ResolvedLocalWorkspaceContext } from "./local-config";
30+import { loadLocalExecutorConfig } from "./local-config";
31+import {
32+ LocalFileSystemError,
33+ unknownLocalErrorDetails,
34+} from "./local-errors";
35+
36+const LOCAL_CONTROL_PLANE_STATE_VERSION = 1 as const;
37+const LOCAL_CONTROL_PLANE_STATE_BASENAME = "control-plane-state.json";
38+
39+const LocalControlPlaneStateSchema = Schema.Struct({
40+ version: Schema.Literal(LOCAL_CONTROL_PLANE_STATE_VERSION),
41+ authArtifacts: Schema.Array(AuthArtifactSchema),
42+ authLeases: Schema.Array(AuthLeaseSchema),
43+ sourceOauthClients: Schema.Array(WorkspaceSourceOauthClientSchema),
44+ sourceAuthSessions: Schema.Array(SourceAuthSessionSchema),
45+ secretMaterials: Schema.Array(SecretMaterialSchema),
46+ executions: Schema.Array(ExecutionSchema),
47+ executionInteractions: Schema.Array(ExecutionInteractionSchema),
48+ executionSteps: Schema.Array(ExecutionStepSchema),
49+});
50+
51+export type LocalControlPlaneState = typeof LocalControlPlaneStateSchema.Type;
52+
53+export type LocalControlPlanePersistence = {
54+ rows: LocalControlPlaneStore;
55+ close: () => Promise<void>;
56+};
57+
58+const decodeLocalControlPlaneState = Schema.decodeUnknownSync(
59+ LocalControlPlaneStateSchema,
60+);
61+
62+const defaultLocalControlPlaneState = (): LocalControlPlaneState => ({
63+ version: LOCAL_CONTROL_PLANE_STATE_VERSION,
64+ authArtifacts: [],
65+ authLeases: [],
66+ sourceOauthClients: [],
67+ sourceAuthSessions: [],
68+ secretMaterials: [],
69+ executions: [],
70+ executionInteractions: [],
71+ executionSteps: [],
72+});
73+
74+const cloneValue = <T>(value: T): T =>
75+ JSON.parse(JSON.stringify(value)) as T;
76+
77+const mapFileSystemError = (path: string, action: string) => (cause: unknown) =>
78+ new LocalFileSystemError({
79+ message: `Failed to ${action} ${path}: ${unknownLocalErrorDetails(cause)}`,
80+ action,
81+ path,
82+ details: unknownLocalErrorDetails(cause),
83+ });
84+
85+const actorEquals = (
86+ left: string | null | undefined,
87+ right: string | null | undefined,
88+): boolean => (left ?? null) === (right ?? null);
89+
90+const sortByUpdatedAtAndIdAsc = <T extends { updatedAt: number; id: string }>(
91+ values: readonly T[],
92+): T[] =>
93+ [...values].sort((left, right) =>
94+ left.updatedAt - right.updatedAt || left.id.localeCompare(right.id),
95+ );
96+
97+const sortByUpdatedAtAndIdDesc = <T extends { updatedAt: number; id: string }>(
98+ values: readonly T[],
99+): T[] =>
100+ [...values].sort((left, right) =>
101+ right.updatedAt - left.updatedAt || right.id.localeCompare(left.id),
102+ );
103+
104+const localControlPlaneStatePath = (
105+ context: ResolvedLocalWorkspaceContext,
106+): string => join(context.stateDirectory, LOCAL_CONTROL_PLANE_STATE_BASENAME);
107+
108+const readStateFromDisk = async (
109+ context: ResolvedLocalWorkspaceContext,
110+): Promise<LocalControlPlaneState> => {
111+ const path = localControlPlaneStatePath(context);
112+ if (!existsSync(path)) {
113+ return defaultLocalControlPlaneState();
114+ }
115+
116+ const content = await readFile(path, "utf8");
117+ return decodeLocalControlPlaneState(JSON.parse(content) as unknown);
118+};
119+
120+const writeStateToDisk = async (
121+ context: ResolvedLocalWorkspaceContext,
122+ state: LocalControlPlaneState,
123+): Promise<void> => {
124+ const path = localControlPlaneStatePath(context);
125+ await mkdir(dirname(path), { recursive: true });
126+ const tempPath = `${path}.${randomUUID()}.tmp`;
127+ await writeFile(tempPath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
128+ await rename(tempPath, path);
129+};
130+
131+export const loadLocalControlPlaneState = (
132+ context: ResolvedLocalWorkspaceContext,
133+): Effect.Effect<LocalControlPlaneState, LocalFileSystemError> =>
134+ Effect.tryPromise({
135+ try: () => readStateFromDisk(context),
136+ catch: mapFileSystemError(
137+ localControlPlaneStatePath(context),
138+ "read control plane state",
139+ ),
140+ });
141+
142+export const writeLocalControlPlaneState = (input: {
143+ context: ResolvedLocalWorkspaceContext;
144+ state: LocalControlPlaneState;
145+}): Effect.Effect<void, LocalFileSystemError> =>
146+ Effect.tryPromise({
147+ try: () => writeStateToDisk(input.context, input.state),
148+ catch: mapFileSystemError(
149+ localControlPlaneStatePath(input.context),
150+ "write control plane state",
151+ ),
152+ });
153+
154+const mergeById = <T extends { id: string }>(
155+ current: readonly T[],
156+ imported: readonly T[],
157+): T[] => {
158+ const merged = new Map<string, T>();
159+
160+ for (const item of imported) {
161+ merged.set(item.id, cloneValue(item));
162+ }
163+
164+ for (const item of current) {
165+ merged.set(item.id, cloneValue(item));
166+ }
167+
168+ return [...merged.values()];
169+};
170+
171+const mergeAuthLeases = (
172+ current: readonly AuthLease[],
173+ imported: readonly AuthLease[],
174+): AuthLease[] => {
175+ const merged = new Map<string, AuthLease>();
176+
177+ for (const lease of imported) {
178+ merged.set(lease.authArtifactId, cloneValue(lease));
179+ }
180+
181+ for (const lease of current) {
182+ merged.set(lease.authArtifactId, cloneValue(lease));
183+ }
184+
185+ return [...merged.values()];
186+};
187+
188+const mergeAuthArtifacts = (
189+ current: readonly AuthArtifact[],
190+ imported: readonly AuthArtifact[],
191+): AuthArtifact[] => {
192+ const merged = new Map<string, AuthArtifact>();
193+
194+ for (const artifact of imported) {
195+ merged.set(
196+ [
197+ artifact.workspaceId,
198+ artifact.sourceId,
199+ artifact.actorAccountId ?? "",
200+ artifact.slot,
201+ ].join("::"),
202+ cloneValue(artifact),
203+ );
204+ }
205+
206+ for (const artifact of current) {
207+ merged.set(
208+ [
209+ artifact.workspaceId,
210+ artifact.sourceId,
211+ artifact.actorAccountId ?? "",
212+ artifact.slot,
213+ ].join("::"),
214+ cloneValue(artifact),
215+ );
216+ }
217+
218+ return [...merged.values()];
219+};
220+
221+const mergeSourceOauthClients = (
222+ current: readonly WorkspaceSourceOauthClient[],
223+ imported: readonly WorkspaceSourceOauthClient[],
224+): WorkspaceSourceOauthClient[] => {
225+ const merged = new Map<string, WorkspaceSourceOauthClient>();
226+
227+ for (const oauthClient of imported) {
228+ merged.set(
229+ [oauthClient.workspaceId, oauthClient.sourceId, oauthClient.providerKey].join(
230+ "::",
231+ ),
232+ cloneValue(oauthClient),
233+ );
234+ }
235+
236+ for (const oauthClient of current) {
237+ merged.set(
238+ [oauthClient.workspaceId, oauthClient.sourceId, oauthClient.providerKey].join(
239+ "::",
240+ ),
241+ cloneValue(oauthClient),
242+ );
243+ }
244+
245+ return [...merged.values()];
246+};
247+
248+export const mergeImportedLocalControlPlaneState = (input: {
249+ current: LocalControlPlaneState;
250+ imported: Partial<Omit<LocalControlPlaneState, "version">>;
251+}): LocalControlPlaneState => ({
252+ version: LOCAL_CONTROL_PLANE_STATE_VERSION,
253+ authArtifacts: mergeAuthArtifacts(
254+ input.current.authArtifacts,
255+ input.imported.authArtifacts ?? [],
256+ ),
257+ authLeases: mergeAuthLeases(
258+ input.current.authLeases,
259+ input.imported.authLeases ?? [],
260+ ),
261+ sourceOauthClients: mergeSourceOauthClients(
262+ input.current.sourceOauthClients,
263+ input.imported.sourceOauthClients ?? [],
264+ ),
265+ sourceAuthSessions: mergeById(
266+ input.current.sourceAuthSessions,
267+ input.imported.sourceAuthSessions ?? [],
268+ ),
269+ secretMaterials: mergeById(
270+ input.current.secretMaterials,
271+ input.imported.secretMaterials ?? [],
272+ ),
273+ executions: mergeById(input.current.executions, input.imported.executions ?? []),
274+ executionInteractions: mergeById(
275+ input.current.executionInteractions,
276+ input.imported.executionInteractions ?? [],
277+ ),
278+ executionSteps: mergeById(
279+ input.current.executionSteps,
280+ input.imported.executionSteps ?? [],
281+ ),
282+});
283+
284+type StateMutationResult<A> = {
285+ state: LocalControlPlaneState;
286+ value: A;
287+};
288+
289+const createStateManager = (context: ResolvedLocalWorkspaceContext) => {
290+ let cache: LocalControlPlaneState | null = null;
291+ let mutationQueue: Promise<void> = Promise.resolve();
292+
293+ const ensureLoaded = async (): Promise<LocalControlPlaneState> => {
294+ if (cache !== null) {
295+ return cache;
296+ }
297+
298+ cache = await readStateFromDisk(context);
299+ return cache;
300+ };
301+
302+ const read = <A>(
303+ operation: (state: LocalControlPlaneState) => A | Promise<A>,
304+ ): Effect.Effect<A, LocalFileSystemError> =>
305+ Effect.tryPromise({
306+ try: async () => operation(cloneValue(await ensureLoaded())),
307+ catch: mapFileSystemError(
308+ localControlPlaneStatePath(context),
309+ "read control plane state",
310+ ),
311+ });
312+
313+ const mutate = <A>(
314+ operation: (
315+ state: LocalControlPlaneState,
316+ ) => StateMutationResult<A> | Promise<StateMutationResult<A>>,
317+ ): Effect.Effect<A, LocalFileSystemError> =>
318+ Effect.tryPromise({
319+ try: async () => {
320+ let value!: A;
321+ let failure: unknown = null;
322+
323+ mutationQueue = mutationQueue.then(async () => {
324+ try {
325+ const current = cloneValue(await ensureLoaded());
326+ const result = await operation(current);
327+ cache = result.state;
328+ value = result.value;
329+ await writeStateToDisk(context, cache);
330+ } catch (cause) {
331+ failure = cause;
332+ }
333+ });
334+
335+ await mutationQueue;
336+
337+ if (failure !== null) {
338+ throw failure;
339+ }
340+
341+ return value;
342+ },
343+ catch: mapFileSystemError(
344+ localControlPlaneStatePath(context),
345+ "write control plane state",
346+ ),
347+ });
348+
349+ return {
350+ read,
351+ mutate,
352+ };
353+};
354+
355+export const createLocalControlPlaneStore = (
356+ context: ResolvedLocalWorkspaceContext,
357+) => {
358+ const stateManager = createStateManager(context);
359+
360+ return {
361+ authArtifacts: {
362+ listByWorkspaceId: (workspaceId: AuthArtifact["workspaceId"]) =>
363+ stateManager.read((state) =>
364+ sortByUpdatedAtAndIdAsc(
365+ state.authArtifacts.filter((artifact) => artifact.workspaceId === workspaceId),
366+ ),
367+ ),
368+
369+ listByWorkspaceAndSourceId: (input: {
370+ workspaceId: AuthArtifact["workspaceId"];
371+ sourceId: AuthArtifact["sourceId"];
372+ }) =>
373+ stateManager.read((state) =>
374+ sortByUpdatedAtAndIdAsc(
375+ state.authArtifacts.filter(
376+ (artifact) =>
377+ artifact.workspaceId === input.workspaceId
378+ && artifact.sourceId === input.sourceId,
379+ ),
380+ ),
381+ ),
382+
383+ getByWorkspaceSourceAndActor: (input: {
384+ workspaceId: AuthArtifact["workspaceId"];
385+ sourceId: AuthArtifact["sourceId"];
386+ actorAccountId: AuthArtifact["actorAccountId"];
387+ slot: AuthArtifact["slot"];
388+ }) =>
389+ stateManager.read((state) => {
390+ const artifact = state.authArtifacts.find(
391+ (candidate) =>
392+ candidate.workspaceId === input.workspaceId
393+ && candidate.sourceId === input.sourceId
394+ && candidate.slot === input.slot
395+ && actorEquals(candidate.actorAccountId, input.actorAccountId),
396+ );
397+
398+ return artifact ? Option.some(cloneValue(artifact)) : Option.none<AuthArtifact>();
399+ }),
400+
401+ upsert: (artifact: AuthArtifact) =>
402+ stateManager.mutate((state) => {
403+ const nextArtifacts = state.authArtifacts.filter(
404+ (candidate) =>
405+ !(
406+ candidate.workspaceId === artifact.workspaceId
407+ && candidate.sourceId === artifact.sourceId
408+ && candidate.slot === artifact.slot
409+ && actorEquals(candidate.actorAccountId, artifact.actorAccountId)
410+ ),
411+ );
412+ nextArtifacts.push(cloneValue(artifact));
413+
414+ return {
415+ state: {
416+ ...state,
417+ authArtifacts: nextArtifacts,
418+ },
419+ value: undefined,
420+ } satisfies StateMutationResult<void>;
421+ }),
422+
423+ removeByWorkspaceSourceAndActor: (input: {
424+ workspaceId: AuthArtifact["workspaceId"];
425+ sourceId: AuthArtifact["sourceId"];
426+ actorAccountId: AuthArtifact["actorAccountId"];
427+ slot?: AuthArtifact["slot"];
428+ }) =>
429+ stateManager.mutate((state) => {
430+ const nextArtifacts = state.authArtifacts.filter(
431+ (candidate) =>
432+ candidate.workspaceId !== input.workspaceId
433+ || candidate.sourceId !== input.sourceId
434+ || !actorEquals(candidate.actorAccountId, input.actorAccountId)
435+ || (input.slot !== undefined && candidate.slot !== input.slot),
436+ );
437+
438+ return {
439+ state: {
440+ ...state,
441+ authArtifacts: nextArtifacts,
442+ },
443+ value: nextArtifacts.length !== state.authArtifacts.length,
444+ } satisfies StateMutationResult<boolean>;
445+ }),
446+
447+ removeByWorkspaceAndSourceId: (input: {
448+ workspaceId: AuthArtifact["workspaceId"];
449+ sourceId: AuthArtifact["sourceId"];
450+ }) =>
451+ stateManager.mutate((state) => {
452+ const nextArtifacts = state.authArtifacts.filter(
453+ (candidate) =>
454+ candidate.workspaceId !== input.workspaceId
455+ || candidate.sourceId !== input.sourceId,
456+ );
457+
458+ return {
459+ state: {
460+ ...state,
461+ authArtifacts: nextArtifacts,
462+ },
463+ value: state.authArtifacts.length - nextArtifacts.length,
464+ } satisfies StateMutationResult<number>;
465+ }),
466+ },
467+
468+ authLeases: {
469+ listAll: () =>
470+ stateManager.read((state) => sortByUpdatedAtAndIdAsc(state.authLeases)),
471+
472+ getByAuthArtifactId: (authArtifactId: AuthLease["authArtifactId"]) =>
473+ stateManager.read((state) => {
474+ const lease = state.authLeases.find(
475+ (candidate) => candidate.authArtifactId === authArtifactId,
476+ );
477+ return lease ? Option.some(cloneValue(lease)) : Option.none<AuthLease>();
478+ }),
479+
480+ upsert: (lease: AuthLease) =>
481+ stateManager.mutate((state) => {
482+ const nextLeases = state.authLeases.filter(
483+ (candidate) => candidate.authArtifactId !== lease.authArtifactId,
484+ );
485+ nextLeases.push(cloneValue(lease));
486+
487+ return {
488+ state: {
489+ ...state,
490+ authLeases: nextLeases,
491+ },
492+ value: undefined,
493+ } satisfies StateMutationResult<void>;
494+ }),
495+
496+ removeByAuthArtifactId: (authArtifactId: AuthLease["authArtifactId"]) =>
497+ stateManager.mutate((state) => {
498+ const nextLeases = state.authLeases.filter(
499+ (candidate) => candidate.authArtifactId !== authArtifactId,
500+ );
501+
502+ return {
503+ state: {
504+ ...state,
505+ authLeases: nextLeases,
506+ },
507+ value: nextLeases.length !== state.authLeases.length,
508+ } satisfies StateMutationResult<boolean>;
509+ }),
510+ },
511+
512+ sourceOauthClients: {
513+ getByWorkspaceSourceAndProvider: (input: {
514+ workspaceId: WorkspaceSourceOauthClient["workspaceId"];
515+ sourceId: WorkspaceSourceOauthClient["sourceId"];
516+ providerKey: string;
517+ }) =>
518+ stateManager.read((state) => {
519+ const oauthClient = state.sourceOauthClients.find(
520+ (candidate) =>
521+ candidate.workspaceId === input.workspaceId
522+ && candidate.sourceId === input.sourceId
523+ && candidate.providerKey === input.providerKey,
524+ );
525+
526+ return oauthClient
527+ ? Option.some(cloneValue(oauthClient))
528+ : Option.none<WorkspaceSourceOauthClient>();
529+ }),
530+
531+ upsert: (oauthClient: WorkspaceSourceOauthClient) =>
532+ stateManager.mutate((state) => {
533+ const nextOauthClients = state.sourceOauthClients.filter(
534+ (candidate) =>
535+ !(
536+ candidate.workspaceId === oauthClient.workspaceId
537+ && candidate.sourceId === oauthClient.sourceId
538+ && candidate.providerKey === oauthClient.providerKey
539+ ),
540+ );
541+ nextOauthClients.push(cloneValue(oauthClient));
542+
543+ return {
544+ state: {
545+ ...state,
546+ sourceOauthClients: nextOauthClients,
547+ },
548+ value: undefined,
549+ } satisfies StateMutationResult<void>;
550+ }),
551+
552+ removeByWorkspaceAndSourceId: (input: {
553+ workspaceId: WorkspaceSourceOauthClient["workspaceId"];
554+ sourceId: WorkspaceSourceOauthClient["sourceId"];
555+ }) =>
556+ stateManager.mutate((state) => {
557+ const nextOauthClients = state.sourceOauthClients.filter(
558+ (candidate) =>
559+ candidate.workspaceId !== input.workspaceId
560+ || candidate.sourceId !== input.sourceId,
561+ );
562+
563+ return {
564+ state: {
565+ ...state,
566+ sourceOauthClients: nextOauthClients,
567+ },
568+ value: state.sourceOauthClients.length - nextOauthClients.length,
569+ } satisfies StateMutationResult<number>;
570+ }),
571+ },
572+
573+ sourceAuthSessions: {
574+ listAll: () =>
575+ stateManager.read((state) => sortByUpdatedAtAndIdAsc(state.sourceAuthSessions)),
576+
577+ listByWorkspaceId: (workspaceId: SourceAuthSession["workspaceId"]) =>
578+ stateManager.read((state) =>
579+ sortByUpdatedAtAndIdAsc(
580+ state.sourceAuthSessions.filter(
581+ (session) => session.workspaceId === workspaceId,
582+ ),
583+ ),
584+ ),
585+
586+ getById: (id: SourceAuthSession["id"]) =>
587+ stateManager.read((state) => {
588+ const session = state.sourceAuthSessions.find(
589+ (candidate) => candidate.id === id,
590+ );
591+ return session
592+ ? Option.some(cloneValue(session))
593+ : Option.none<SourceAuthSession>();
594+ }),
595+
596+ getByState: (stateValue: SourceAuthSession["state"]) =>
597+ stateManager.read((state) => {
598+ const session = state.sourceAuthSessions.find(
599+ (candidate) => candidate.state === stateValue,
600+ );
601+ return session
602+ ? Option.some(cloneValue(session))
603+ : Option.none<SourceAuthSession>();
604+ }),
605+
606+ getPendingByWorkspaceSourceAndActor: (input: {
607+ workspaceId: SourceAuthSession["workspaceId"];
608+ sourceId: SourceAuthSession["sourceId"];
609+ actorAccountId: SourceAuthSession["actorAccountId"];
610+ credentialSlot?: SourceAuthSession["credentialSlot"];
611+ }) =>
612+ stateManager.read((state) => {
613+ const session = sortByUpdatedAtAndIdAsc(
614+ state.sourceAuthSessions.filter(
615+ (candidate) =>
616+ candidate.workspaceId === input.workspaceId
617+ && candidate.sourceId === input.sourceId
618+ && candidate.status === "pending"
619+ && actorEquals(candidate.actorAccountId, input.actorAccountId)
620+ && (input.credentialSlot === undefined
621+ || candidate.credentialSlot === input.credentialSlot),
622+ ),
623+ )[0] ?? null;
624+
625+ return session
626+ ? Option.some(cloneValue(session))
627+ : Option.none<SourceAuthSession>();
628+ }),
629+
630+ insert: (session: SourceAuthSession) =>
631+ stateManager.mutate((state) => ({
632+ state: {
633+ ...state,
634+ sourceAuthSessions: [...state.sourceAuthSessions, cloneValue(session)],
635+ },
636+ value: undefined,
637+ } satisfies StateMutationResult<void>)),
638+
639+ update: (
640+ id: SourceAuthSession["id"],
641+ patch: Partial<Omit<SourceAuthSession, "id" | "workspaceId" | "sourceId" | "createdAt">>,
642+ ) =>
643+ stateManager.mutate((state) => {
644+ let updated: SourceAuthSession | null = null;
645+ const nextSessions = state.sourceAuthSessions.map((session) => {
646+ if (session.id !== id) {
647+ return session;
648+ }
649+
650+ updated = {
651+ ...session,
652+ ...cloneValue(patch),
653+ } satisfies SourceAuthSession;
654+ return updated;
655+ });
656+
657+ return {
658+ state: {
659+ ...state,
660+ sourceAuthSessions: nextSessions,
661+ },
662+ value: updated ? Option.some(cloneValue(updated)) : Option.none<SourceAuthSession>(),
663+ } satisfies StateMutationResult<Option.Option<SourceAuthSession>>;
664+ }),
665+
666+ upsert: (session: SourceAuthSession) =>
667+ stateManager.mutate((state) => {
668+ const nextSessions = state.sourceAuthSessions.filter(
669+ (candidate) => candidate.id !== session.id,
670+ );
671+ nextSessions.push(cloneValue(session));
672+
673+ return {
674+ state: {
675+ ...state,
676+ sourceAuthSessions: nextSessions,
677+ },
678+ value: undefined,
679+ } satisfies StateMutationResult<void>;
680+ }),
681+
682+ removeByWorkspaceAndSourceId: (
683+ workspaceId: SourceAuthSession["workspaceId"],
684+ sourceId: SourceAuthSession["sourceId"],
685+ ) =>
686+ stateManager.mutate((state) => {
687+ const nextSessions = state.sourceAuthSessions.filter(
688+ (candidate) =>
689+ candidate.workspaceId !== workspaceId || candidate.sourceId !== sourceId,
690+ );
691+
692+ return {
693+ state: {
694+ ...state,
695+ sourceAuthSessions: nextSessions,
696+ },
697+ value: nextSessions.length !== state.sourceAuthSessions.length,
698+ } satisfies StateMutationResult<boolean>;
699+ }),
700+ },
701+
702+ secretMaterials: {
703+ getById: (id: SecretMaterial["id"]) =>
704+ stateManager.read((state) => {
705+ const material = state.secretMaterials.find(
706+ (candidate) => candidate.id === id,
707+ );
708+ return material
709+ ? Option.some(cloneValue(material))
710+ : Option.none<SecretMaterial>();
711+ }),
712+
713+ listAll: () =>
714+ stateManager.read((state) => sortByUpdatedAtAndIdDesc(state.secretMaterials)),
715+
716+ upsert: (material: SecretMaterial) =>
717+ stateManager.mutate((state) => {
718+ const nextMaterials = state.secretMaterials.filter(
719+ (candidate) => candidate.id !== material.id,
720+ );
721+ nextMaterials.push(cloneValue(material));
722+
723+ return {
724+ state: {
725+ ...state,
726+ secretMaterials: nextMaterials,
727+ },
728+ value: undefined,
729+ } satisfies StateMutationResult<void>;
730+ }),
731+
732+ updateById: (
733+ id: SecretMaterial["id"],
734+ update: { name?: string | null; value?: string },
735+ ) =>
736+ stateManager.mutate((state) => {
737+ let updated: SecretMaterial | null = null;
738+ const nextMaterials = state.secretMaterials.map((material) => {
739+ if (material.id !== id) {
740+ return material;
741+ }
742+
743+ updated = {
744+ ...material,
745+ ...(update.name !== undefined ? { name: update.name } : {}),
746+ ...(update.value !== undefined ? { value: update.value } : {}),
747+ updatedAt: Date.now(),
748+ } satisfies SecretMaterial;
749+ return updated;
750+ });
751+
752+ return {
753+ state: {
754+ ...state,
755+ secretMaterials: nextMaterials,
756+ },
757+ value: updated ? Option.some(cloneValue(updated)) : Option.none<SecretMaterial>(),
758+ } satisfies StateMutationResult<Option.Option<SecretMaterial>>;
759+ }),
760+
761+ removeById: (id: SecretMaterial["id"]) =>
762+ stateManager.mutate((state) => {
763+ const nextMaterials = state.secretMaterials.filter(
764+ (candidate) => candidate.id !== id,
765+ );
766+
767+ return {
768+ state: {
769+ ...state,
770+ secretMaterials: nextMaterials,
771+ },
772+ value: nextMaterials.length !== state.secretMaterials.length,
773+ } satisfies StateMutationResult<boolean>;
774+ }),
775+
776+ listLinkedSources: () =>
777+ Effect.gen(function* () {
778+ const [state, loadedConfig] = yield* Effect.all([
779+ stateManager.read((current) => current),
780+ loadLocalExecutorConfig(context),
781+ ]);
782+
783+ const sourceNames = new Map<string, string>();
784+ for (const [sourceId, source] of Object.entries(
785+ loadedConfig.config?.sources ?? {},
786+ )) {
787+ sourceNames.set(sourceId, source.name ?? sourceId);
788+ }
789+
790+ const materialIds = new Set(
791+ state.secretMaterials.map((material) => String(material.id)),
792+ );
793+ const linkedSources = new Map<string, Array<{ sourceId: string; sourceName: string }>>();
794+
795+ for (const artifact of state.authArtifacts) {
796+ for (const ref of authArtifactSecretMaterialRefs(artifact)) {
797+ if (!materialIds.has(ref.handle)) {
798+ continue;
799+ }
800+
801+ const existing = linkedSources.get(ref.handle) ?? [];
802+ if (!existing.some((link) => link.sourceId === artifact.sourceId)) {
803+ existing.push({
804+ sourceId: artifact.sourceId,
805+ sourceName: sourceNames.get(artifact.sourceId) ?? artifact.sourceId,
806+ });
807+ linkedSources.set(ref.handle, existing);
808+ }
809+ }
810+ }
811+
812+ return linkedSources;
813+ }),
814+ },
815+
816+ executions: {
817+ getById: (executionId: Execution["id"]) =>
818+ stateManager.read((state) => {
819+ const execution = state.executions.find(
820+ (candidate) => candidate.id === executionId,
821+ );
822+ return execution
823+ ? Option.some(cloneValue(execution))
824+ : Option.none<Execution>();
825+ }),
826+
827+ getByWorkspaceAndId: (
828+ workspaceId: Execution["workspaceId"],
829+ executionId: Execution["id"],
830+ ) =>
831+ stateManager.read((state) => {
832+ const execution = state.executions.find(
833+ (candidate) =>
834+ candidate.workspaceId === workspaceId && candidate.id === executionId,
835+ );
836+ return execution
837+ ? Option.some(cloneValue(execution))
838+ : Option.none<Execution>();
839+ }),
840+
841+ insert: (execution: Execution) =>
842+ stateManager.mutate((state) => ({
843+ state: {
844+ ...state,
845+ executions: [...state.executions, cloneValue(execution)],
846+ },
847+ value: undefined,
848+ } satisfies StateMutationResult<void>)),
849+
850+ update: (
851+ executionId: Execution["id"],
852+ patch: Partial<
853+ Omit<Execution, "id" | "workspaceId" | "createdByAccountId" | "createdAt">
854+ >,
855+ ) =>
856+ stateManager.mutate((state) => {
857+ let updated: Execution | null = null;
858+ const nextExecutions = state.executions.map((execution) => {
859+ if (execution.id !== executionId) {
860+ return execution;
861+ }
862+
863+ updated = {
864+ ...execution,
865+ ...cloneValue(patch),
866+ } satisfies Execution;
867+ return updated;
868+ });
869+
870+ return {
871+ state: {
872+ ...state,
873+ executions: nextExecutions,
874+ },
875+ value: updated ? Option.some(cloneValue(updated)) : Option.none<Execution>(),
876+ } satisfies StateMutationResult<Option.Option<Execution>>;
877+ }),
878+ },
879+
880+ executionInteractions: {
881+ getById: (interactionId: ExecutionInteraction["id"]) =>
882+ stateManager.read((state) => {
883+ const interaction = state.executionInteractions.find(
884+ (candidate) => candidate.id === interactionId,
885+ );
886+ return interaction
887+ ? Option.some(cloneValue(interaction))
888+ : Option.none<ExecutionInteraction>();
889+ }),
890+
891+ listByExecutionId: (executionId: ExecutionInteraction["executionId"]) =>
892+ stateManager.read((state) =>
893+ sortByUpdatedAtAndIdDesc(
894+ state.executionInteractions.filter(
895+ (interaction) => interaction.executionId === executionId,
896+ ),
897+ ),
898+ ),
899+
900+ getPendingByExecutionId: (executionId: ExecutionInteraction["executionId"]) =>
901+ stateManager.read((state) => {
902+ const interaction = sortByUpdatedAtAndIdDesc(
903+ state.executionInteractions.filter(
904+ (candidate) =>
905+ candidate.executionId === executionId && candidate.status === "pending",
906+ ),
907+ )[0] ?? null;
908+
909+ return interaction
910+ ? Option.some(cloneValue(interaction))
911+ : Option.none<ExecutionInteraction>();
912+ }),
913+
914+ insert: (interaction: ExecutionInteraction) =>
915+ stateManager.mutate((state) => ({
916+ state: {
917+ ...state,
918+ executionInteractions: [
919+ ...state.executionInteractions,
920+ cloneValue(interaction),
921+ ],
922+ },
923+ value: undefined,
924+ } satisfies StateMutationResult<void>)),
925+
926+ update: (
927+ interactionId: ExecutionInteraction["id"],
928+ patch: Partial<
929+ Omit<
930+ ExecutionInteraction,
931+ "id" | "executionId" | "createdAt"
932+ >
933+ >,
934+ ) =>
935+ stateManager.mutate((state) => {
936+ let updated: ExecutionInteraction | null = null;
937+ const nextInteractions = state.executionInteractions.map((interaction) => {
938+ if (interaction.id !== interactionId) {
939+ return interaction;
940+ }
941+
942+ updated = {
943+ ...interaction,
944+ ...cloneValue(patch),
945+ } as ExecutionInteraction;
946+ return updated;
947+ });
948+
949+ return {
950+ state: {
951+ ...state,
952+ executionInteractions: nextInteractions,
953+ },
954+ value: updated
955+ ? Option.some(cloneValue(updated))
956+ : Option.none<ExecutionInteraction>(),
957+ } satisfies StateMutationResult<Option.Option<ExecutionInteraction>>;
958+ }),
959+ },
960+
961+ executionSteps: {
962+ getByExecutionAndSequence: (
963+ executionId: ExecutionStep["executionId"],
964+ sequence: ExecutionStep["sequence"],
965+ ) =>
966+ stateManager.read((state) => {
967+ const step = state.executionSteps.find(
968+ (candidate) =>
969+ candidate.executionId === executionId && candidate.sequence === sequence,
970+ );
971+ return step
972+ ? Option.some(cloneValue(step))
973+ : Option.none<ExecutionStep>();
974+ }),
975+
976+ listByExecutionId: (executionId: ExecutionStep["executionId"]) =>
977+ stateManager.read((state) =>
978+ [...state.executionSteps]
979+ .filter((step) => step.executionId === executionId)
980+ .sort(
981+ (left, right) =>
982+ left.sequence - right.sequence
983+ || right.updatedAt - left.updatedAt,
984+ ),
985+ ),
986+
987+ insert: (step: ExecutionStep) =>
988+ stateManager.mutate((state) => ({
989+ state: {
990+ ...state,
991+ executionSteps: [...state.executionSteps, cloneValue(step)],
992+ },
993+ value: undefined,
994+ } satisfies StateMutationResult<void>)),
995+
996+ deleteByExecutionId: (executionId: ExecutionStep["executionId"]) =>
997+ stateManager.mutate((state) => ({
998+ state: {
999+ ...state,
1000+ executionSteps: state.executionSteps.filter(
1001+ (step) => step.executionId !== executionId,
1002+ ),
1003+ },
1004+ value: undefined,
1005+ } satisfies StateMutationResult<void>)),
1006+
1007+ updateByExecutionAndSequence: (
1008+ executionId: ExecutionStep["executionId"],
1009+ sequence: ExecutionStep["sequence"],
1010+ patch: Partial<
1011+ Omit<
1012+ ExecutionStep,
1013+ "id" | "executionId" | "sequence" | "createdAt"
1014+ >
1015+ >,
1016+ ) =>
1017+ stateManager.mutate((state) => {
1018+ let updated: ExecutionStep | null = null;
1019+ const nextSteps = state.executionSteps.map((step) => {
1020+ if (step.executionId !== executionId || step.sequence !== sequence) {
1021+ return step;
1022+ }
1023+
1024+ updated = {
1025+ ...step,
1026+ ...cloneValue(patch),
1027+ } as ExecutionStep;
1028+ return updated;
1029+ });
1030+
1031+ return {
1032+ state: {
1033+ ...state,
1034+ executionSteps: nextSteps,
1035+ },
1036+ value: updated
1037+ ? Option.some(cloneValue(updated))
1038+ : Option.none<ExecutionStep>(),
1039+ } satisfies StateMutationResult<Option.Option<ExecutionStep>>;
1040+ }),
1041+ },
1042+ };
1043+};
1044+
1045+export type LocalControlPlaneStore = ReturnType<typeof createLocalControlPlaneStore>;
1046+
1047+export const createLocalControlPlanePersistence = (
1048+ context: ResolvedLocalWorkspaceContext,
1049+): LocalControlPlanePersistence => ({
1050+ rows: createLocalControlPlaneStore(context),
1051+ close: async () => {},
1052+});
1053+
1054+export { localControlPlaneStatePath };
packages/control-plane/src/runtime/local-operations.ts+16−15
@@ -1,4 +1,3 @@
1−import { ControlPlanePersistenceError } from "#persistence";
21 import type {
32 Execution,
43 ExecutionInteraction,
@@ -12,13 +11,13 @@ import { · 7 unmodified lines
1211 import * as Effect from "effect/Effect";
1312 import * as Option from "effect/Option";
1413
15−import { loadLocalInstallation } from "./local-installation";
1614 import { submitExecutionInteractionResponse } from "./execution-service";
1715 import { LiveExecutionManagerService } from "./live-execution";
1816 import {
1917 operationErrors,
2018 } from "./operation-errors";
2119 import { resolveLocalWorkspaceContext } from "./local-config";
20+import { InstallationStore, LocalInstallationStore } from "./local-storage";
2221 import { getRuntimeLocalWorkspaceOption } from "./local-runtime-context";
2322 import { RuntimeSourceAuthServiceTag } from "./source-auth-service";
2423 import {
@@ -130,12 +129,10 @@ const loadSourceCredentialInteraction = (input: { · 105 unmodified lines
130129
131130 const stored = yield* store.executionInteractions.getById(input.interactionId).pipe(
132131 Effect.mapError((error) =>
133− error instanceof ControlPlanePersistenceError
134− ? input.operation.storage(error)
135− : input.operation.unknownStorage(
136− error,
137− `Failed loading execution interaction ${input.interactionId}`,
138− ),
132+ input.operation.unknownStorage(
133+ error,
134+ `Failed loading execution interaction ${input.interactionId}`,
135+ ),
139136 ),
140137 );
141138
@@ -194,7 +191,13 @@ export const getLocalInstallation = () => · 52 unmodified lines
194191 ),
195192 ));
196193
197− return yield* loadLocalInstallation(context);
194+ const installationStore = runtimeLocalWorkspace !== null
195+ ? yield* InstallationStore
196+ : LocalInstallationStore;
197+
198+ return yield* installationStore.load(
199+ context,
200+ );
198201 });
199202
200203 export const getSourceCredentialInteraction = (input: {
@@ -331,12 +334,10 @@ export const submitSourceCredentialInteraction = (input: { · 130 unmodified lines
331334 value: token,
332335 }).pipe(
333336 Effect.mapError((error) =>
334− error instanceof ControlPlanePersistenceError
335− ? localOps.sourceCredentialSubmit.storage(error)
336− : localOps.sourceCredentialSubmit.unknownStorage(
337− error,
338− `Failed storing credential material for interaction ${interaction.interactionId}`,
339− ),
337+ localOps.sourceCredentialSubmit.unknownStorage(
338+ error,
339+ `Failed storing credential material for interaction ${interaction.interactionId}`,
340+ ),
340341 ),
341342 );
342343 const resumed = yield* liveExecutionManager.resolveInteraction({
packages/control-plane/src/runtime/local-storage.ts+154−0
@@ -0,0 +1,154 @@
1+import type { LocalInstallation, LocalExecutorConfig } from "#schema";
2+import * as Context from "effect/Context";
3+import type * as Effect from "effect/Effect";
4+import * as Layer from "effect/Layer";
5+
6+import type {
7+ LoadedLocalExecutorConfig,
8+ ResolvedLocalWorkspaceContext,
9+} from "./local-config";
10+import {
11+ loadLocalExecutorConfig,
12+ resolveConfigRelativePath,
13+ writeProjectLocalExecutorConfig,
14+} from "./local-config";
15+import {
16+ getOrProvisionLocalInstallation,
17+ loadLocalInstallation,
18+} from "./local-installation";
19+import type {
20+ LocalSourceArtifact,
21+} from "./local-source-artifacts";
22+import {
23+ buildLocalSourceArtifact,
24+ readLocalSourceArtifact,
25+ removeLocalSourceArtifact,
26+ writeLocalSourceArtifact,
27+} from "./local-source-artifacts";
28+import type { LocalWorkspaceState } from "./local-workspace-state";
29+import {
30+ loadLocalWorkspaceState,
31+ writeLocalWorkspaceState,
32+} from "./local-workspace-state";
33+import type { SourceRecipeMaterialization } from "./source-recipe-support";
34+import type { Source } from "#schema";
35+
36+export type InstallationStoreShape = {
37+ load: (
38+ context: ResolvedLocalWorkspaceContext,
39+ ) => Effect.Effect<LocalInstallation, never, never>;
40+ getOrProvision: (input: {
41+ context: ResolvedLocalWorkspaceContext;
42+ }) => Effect.Effect<LocalInstallation, never, never>;
43+};
44+
45+export class InstallationStore extends Context.Tag(
46+ "#runtime/InstallationStore",
47+)<InstallationStore, InstallationStoreShape>() {}
48+
49+export type WorkspaceConfigStoreShape = {
50+ load: (
51+ context: ResolvedLocalWorkspaceContext,
52+ ) => Effect.Effect<LoadedLocalExecutorConfig, Error, never>;
53+ writeProject: (input: {
54+ context: ResolvedLocalWorkspaceContext;
55+ config: LocalExecutorConfig;
56+ }) => Effect.Effect<void, Error, never>;
57+ resolveRelativePath: (input: { path: string; workspaceRoot: string }) => string;
58+};
59+
60+export class WorkspaceConfigStore extends Context.Tag(
61+ "#runtime/WorkspaceConfigStore",
62+)<WorkspaceConfigStore, WorkspaceConfigStoreShape>() {}
63+
64+export type WorkspaceStateStoreShape = {
65+ load: (
66+ context: ResolvedLocalWorkspaceContext,
67+ ) => Effect.Effect<LocalWorkspaceState, Error, never>;
68+ write: (input: {
69+ context: ResolvedLocalWorkspaceContext;
70+ state: LocalWorkspaceState;
71+ }) => Effect.Effect<void, Error, never>;
72+};
73+
74+export class WorkspaceStateStore extends Context.Tag(
75+ "#runtime/WorkspaceStateStore",
76+)<WorkspaceStateStore, WorkspaceStateStoreShape>() {}
77+
78+export type SourceArtifactStoreShape = {
79+ build: (input: {
80+ source: Source;
81+ materialization: SourceRecipeMaterialization;
82+ }) => LocalSourceArtifact;
83+ read: (input: {
84+ context: ResolvedLocalWorkspaceContext;
85+ sourceId: string;
86+ }) => Effect.Effect<LocalSourceArtifact | null, Error, never>;
87+ write: (input: {
88+ context: ResolvedLocalWorkspaceContext;
89+ sourceId: string;
90+ artifact: LocalSourceArtifact;
91+ }) => Effect.Effect<void, Error, never>;
92+ remove: (input: {
93+ context: ResolvedLocalWorkspaceContext;
94+ sourceId: string;
95+ }) => Effect.Effect<void, Error, never>;
96+};
97+
98+export class SourceArtifactStore extends Context.Tag(
99+ "#runtime/SourceArtifactStore",
100+)<SourceArtifactStore, SourceArtifactStoreShape>() {}
101+
102+export type LocalStorageServices =
103+ | InstallationStore
104+ | WorkspaceConfigStore
105+ | WorkspaceStateStore
106+ | SourceArtifactStore;
107+
108+export type WorkspaceStorageServices =
109+ | WorkspaceConfigStore
110+ | WorkspaceStateStore
111+ | SourceArtifactStore;
112+
113+export const LocalInstallationStore: InstallationStoreShape = {
114+ load: loadLocalInstallation,
115+ getOrProvision: getOrProvisionLocalInstallation,
116+};
117+
118+export const LocalInstallationStoreLive = Layer.succeed(
119+ InstallationStore,
120+ LocalInstallationStore,
121+);
122+
123+export const LocalWorkspaceConfigStore: WorkspaceConfigStoreShape = {
124+ load: loadLocalExecutorConfig,
125+ writeProject: writeProjectLocalExecutorConfig,
126+ resolveRelativePath: resolveConfigRelativePath,
127+};
128+
129+export const LocalWorkspaceConfigStoreLive = Layer.succeed(
130+ WorkspaceConfigStore,
131+ LocalWorkspaceConfigStore,
132+);
133+
134+export const LocalWorkspaceStateStore: WorkspaceStateStoreShape = {
135+ load: loadLocalWorkspaceState,
136+ write: writeLocalWorkspaceState,
137+};
138+
139+export const LocalWorkspaceStateStoreLive = Layer.succeed(
140+ WorkspaceStateStore,
141+ LocalWorkspaceStateStore,
142+);
143+
144+export const LocalSourceArtifactStore: SourceArtifactStoreShape = {
145+ build: buildLocalSourceArtifact,
146+ read: readLocalSourceArtifact,
147+ write: writeLocalSourceArtifact,
148+ remove: removeLocalSourceArtifact,
149+};
150+
151+export const LocalSourceArtifactStoreLive = Layer.succeed(
152+ SourceArtifactStore,
153+ LocalSourceArtifactStore,
154+);
packages/control-plane/src/runtime/local-tools.ts+1−1
@@ -540,7 +540,7 @@ const importLocalToolModule = (input: { · 539 unmodified lines
540540 toolPath: ToolPath;
541541 }): Effect.Effect<ToolInput, LocalToolImportError | LocalToolDefinitionError> =>
542542 Effect.tryPromise({
543− try: () => import(pathToFileURL(resolve(input.artifactPath)).href),
543+ try: () => import(/* @vite-ignore */ pathToFileURL(resolve(input.artifactPath)).href),
544544 catch: (cause) =>
545545 new LocalToolImportError({
546546 message: `Failed to import local tool ${input.sourcePath}`,
packages/control-plane/src/runtime/operation-errors.ts+10−29
@@ -3,25 +3,8 @@ import { · 2 unmodified lines
33 ControlPlaneNotFoundError,
44 ControlPlaneStorageError,
55 } from "../api/errors";
6−import { ControlPlanePersistenceError } from "#persistence";
76 import * as Effect from "effect/Effect";
87
9−const unknownPersistenceError = (
10− operation: string,
11− cause: unknown,
12− details: string,
13−): ControlPlanePersistenceError =>
14− new ControlPlanePersistenceError({
15− operation,
16− message: cause instanceof Error ? cause.message : String(cause),
17− details,
18− code: null,
19− constraint: null,
20− table: null,
21− kind: "unknown",
22− cause,
23− });
24−
258 export type OperationErrors<TOperation extends string = string> = {
269 readonly operation: TOperation;
2710 readonly child: <TSuffix extends string>(
@@ -36,18 +19,14 @@ export type OperationErrors<TOperation extends string = string> = { · 8 unmodified lines
3619 details: string,
3720 ) => ControlPlaneNotFoundError;
3821 readonly storage: (
39− error: ControlPlanePersistenceError,
22+ error: Error,
4023 ) => ControlPlaneStorageError;
41− readonly unknownPersistence: (
42− cause: unknown,
43− details: string,
44− ) => ControlPlanePersistenceError;
4524 readonly unknownStorage: (
4625 cause: unknown,
4726 details: string,
4827 ) => ControlPlaneStorageError;
49− readonly mapStorage: <A>(
50− effect: Effect.Effect<A, ControlPlanePersistenceError>,
28+ readonly mapStorage: <A, E extends Error>(
29+ effect: Effect.Effect<A, E>,
5130 ) => Effect.Effect<A, ControlPlaneStorageError>;
5231 };
5332
@@ -76,15 +55,17 @@ export const operationErrors = <TOperation extends string>( · 22 unmodified lines
7655 new ControlPlaneStorageError({
7756 operation,
7857 message: error.message,
79− details: error.details ?? "Persistence operation failed",
58+ details: error.message,
8059 }),
81− unknownPersistence: (cause, details) =>
82− unknownPersistenceError(operation, cause, details),
8360 unknownStorage: (cause, details) =>
84− self.storage(self.unknownPersistence(cause, details)),
61+ self.storage(
62+ cause instanceof Error
63+ ? new Error(`${cause.message}: ${details}`)
64+ : new Error(details),
65+ ),
8566 mapStorage: (effect) =>
8667 effect.pipe(
87− Effect.mapError(self.storage),
68+ Effect.mapError((error) => self.storage(error)),
8869 ),
8970 };
9071
packages/control-plane/src/runtime/operations-shared.ts+4−17
@@ -2,7 +2,6 @@ import { · 1 unmodified line
22 ControlPlaneBadRequestError,
33 ControlPlaneStorageError,
44 } from "../api/errors";
5−import { ControlPlanePersistenceError } from "#persistence";
65 import * as Effect from "effect/Effect";
76
87 import {
@@ -11,26 +10,14 @@ import { · 2 unmodified lines
1110 } from "./operation-errors";
1211 export type Mutable<T> = { -readonly [K in keyof T]: T[K] };
1312
14−const isUniqueViolation = (error: ControlPlanePersistenceError): boolean =>
15− error.kind === "unique_violation";
16−
17−export const mapPersistenceError = <A>(
13+export const mapPersistenceError = <A, R>(
1814 operation: OperationErrorsLike,
19− effect: Effect.Effect<A, ControlPlanePersistenceError | Error>,
20−): Effect.Effect<A, ControlPlaneBadRequestError | ControlPlaneStorageError> =>
15+ effect: Effect.Effect<A, Error, R>,
16+): Effect.Effect<A, ControlPlaneBadRequestError | ControlPlaneStorageError, R> =>
2117 effect.pipe(
2218 Effect.mapError((error) => {
2319 const errors = asOperationErrors(operation);
24− if (error instanceof ControlPlanePersistenceError) {
25− return isUniqueViolation(error)
26− ? errors.badRequest("Unique constraint violation", error.details ?? "duplicate key")
27− : errors.storage(error);
28− }
29−
30− return errors.unknownStorage(
31− error,
32− error.message,
33− );
20+ return errors.storage(error);
3421 }),
3522 );
3623
packages/control-plane/src/runtime/policies-operations.ts+35−11
@@ -13,17 +13,21 @@ import { · 12 unmodified lines
1313 } from "#schema";
1414 import * as Effect from "effect/Effect";
1515
16−import {
17− loadLocalExecutorConfig,
18− writeProjectLocalExecutorConfig,
19−} from "./local-config";
2016 import {
2117 requireRuntimeLocalWorkspace,
2218 } from "./local-runtime-context";
19+import type {
20+ WorkspaceStorageServices,
21+ WorkspaceConfigStoreShape,
22+ WorkspaceStateStoreShape,
23+} from "./local-storage";
24+import {
25+ WorkspaceConfigStore,
26+ WorkspaceStateStore,
27+} from "./local-storage";
28+import type { ResolvedLocalWorkspaceContext } from "./local-config";
2329 import {
24− loadLocalWorkspaceState,
2530 type LocalWorkspaceState,
26− writeLocalWorkspaceState,
2731 } from "./local-workspace-state";
2832 import {
2933 derivePolicyConfigKey,
@@ -77,8 +81,14 @@ const toLocalWorkspacePolicy = (input: { · 47 unmodified lines
7781 export const loadRuntimeLocalWorkspacePolicies = (workspaceId: WorkspaceId) =>
7882 Effect.gen(function* () {
7983 const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(workspaceId);
80− const loadedConfig = yield* loadLocalExecutorConfig(runtimeLocalWorkspace.context);
81− const workspaceState = yield* loadLocalWorkspaceState(runtimeLocalWorkspace.context);
84+ const workspaceConfigStore = yield* WorkspaceConfigStore;
85+ const workspaceStateStore = yield* WorkspaceStateStore;
86+ const loadedConfig = yield* workspaceConfigStore.load(
87+ runtimeLocalWorkspace.context,
88+ );
89+ const workspaceState = yield* workspaceStateStore.load(
90+ runtimeLocalWorkspace.context,
91+ );
8292
8393 const policies = Object.entries(loadedConfig.config?.policies ?? {}).map(([key, policyConfig]) =>
8494 toLocalWorkspacePolicy({
@@ -99,16 +109,18 @@ export const loadRuntimeLocalWorkspacePolicies = (workspaceId: WorkspaceId) => · 14 unmodified lines
99109
100110 const writeLocalPolicyFiles = (input: {
101111 operation: OperationErrors;
102− context: Parameters<typeof writeProjectLocalExecutorConfig>[0]["context"];
112+ context: ResolvedLocalWorkspaceContext;
113+ workspaceConfigStore: WorkspaceConfigStoreShape;
114+ workspaceStateStore: WorkspaceStateStoreShape;
103115 projectConfig: LocalExecutorConfig;
104116 workspaceState: LocalWorkspaceState;
105117 }) =>
106118 Effect.all([
107− writeProjectLocalExecutorConfig({
119+ input.workspaceConfigStore.writeProject({
108120 context: input.context,
109121 config: input.projectConfig,
110122 }),
111− writeLocalWorkspaceState({
123+ input.workspaceStateStore.write({
112124 context: input.context,
113125 state: input.workspaceState,
114126 }),
@@ -154,6 +166,8 @@ export const createPolicy = (input: { · 39 unmodified lines
154166 }) =>
155167 Effect.gen(function* () {
156168 const runtimeLocalWorkspace = yield* loadWorkspacePolicyContext(policyOps.create, input.workspaceId);
169+ const workspaceConfigStore = yield* WorkspaceConfigStore;
170+ const workspaceStateStore = yield* WorkspaceStateStore;
157171 const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(input.workspaceId).pipe(
158172 Effect.mapError((cause) =>
159173 policyOps.create.unknownStorage(
@@ -201,6 +215,8 @@ export const createPolicy = (input: { · 41 unmodified lines
201215 yield* writeLocalPolicyFiles({
202216 operation: policyOps.create,
203217 context: runtimeLocalWorkspace.context,
218+ workspaceConfigStore,
219+ workspaceStateStore,
204220 projectConfig: {
205221 ...projectConfig,
206222 policies,
@@ -250,6 +266,8 @@ export const updatePolicy = (input: { · 43 unmodified lines
250266 }) =>
251267 Effect.gen(function* () {
252268 const runtimeLocalWorkspace = yield* loadWorkspacePolicyContext(policyOps.update, input.workspaceId);
269+ const workspaceConfigStore = yield* WorkspaceConfigStore;
270+ const workspaceStateStore = yield* WorkspaceStateStore;
253271 const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(input.workspaceId).pipe(
254272 Effect.mapError((cause) =>
255273 policyOps.update.unknownStorage(
@@ -300,6 +318,8 @@ export const updatePolicy = (input: { · 44 unmodified lines
300318 yield* writeLocalPolicyFiles({
301319 operation: policyOps.update,
302320 context: runtimeLocalWorkspace.context,
321+ workspaceConfigStore,
322+ workspaceStateStore,
303323 projectConfig: {
304324 ...projectConfig,
305325 policies,
@@ -322,6 +342,8 @@ export const removePolicy = (input: { · 16 unmodified lines
322342 }) =>
323343 Effect.gen(function* () {
324344 const runtimeLocalWorkspace = yield* loadWorkspacePolicyContext(policyOps.remove, input.workspaceId);
345+ const workspaceConfigStore = yield* WorkspaceConfigStore;
346+ const workspaceStateStore = yield* WorkspaceStateStore;
325347 const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(input.workspaceId).pipe(
326348 Effect.mapError((cause) =>
327349 policyOps.remove.unknownStorage(
@@ -343,6 +365,8 @@ export const removePolicy = (input: { · 15 unmodified lines
343365 yield* writeLocalPolicyFiles({
344366 operation: policyOps.remove,
345367 context: runtimeLocalWorkspace.context,
368+ workspaceConfigStore,
369+ workspaceStateStore,
346370 projectConfig: {
347371 ...projectConfig,
348372 policies,
packages/control-plane/src/runtime/secret-material-providers.ts+16−14
@@ -4,7 +4,6 @@ import { promises as fs } from "node:fs"; · 3 unmodified lines
44 import { lstatSync, realpathSync } from "node:fs";
55 import { isAbsolute } from "node:path";
66
7−import type { SqlControlPlaneRows } from "#persistence";
87 import {
98 type LocalConfigSecretProvider,
109 type LocalExecutorConfig,
@@ -17,15 +16,16 @@ import * as Option from "effect/Option"; · 6 unmodified lines
1716
1817 import { resolveConfigRelativePath } from "./local-config";
1918 import { fromConfigSecretProviderId } from "./local-config-secrets";
19+import type { ControlPlaneStoreShape } from "./store";
2020
2121 export const ENV_SECRET_PROVIDER_ID = "env";
2222 export const PARAMS_SECRET_PROVIDER_ID = "params";
2323 export const KEYCHAIN_SECRET_PROVIDER_ID = "keychain";
24−export const POSTGRES_SECRET_PROVIDER_ID = "postgres";
24+export const LOCAL_SECRET_PROVIDER_ID = "local";
2525
2626 export type SecretStoreProviderId =
2727 | typeof KEYCHAIN_SECRET_PROVIDER_ID
28− | typeof POSTGRES_SECRET_PROVIDER_ID;
28+ | typeof LOCAL_SECRET_PROVIDER_ID;
2929
3030 export type SecretMaterialResolveContext = {
3131 params?: Readonly<Record<string, string | undefined>>;
@@ -47,7 +47,7 @@ export type DeleteSecretMaterial = ( · 15 unmodified lines
4747 ) => Effect.Effect<boolean, Error, never>;
4848
4949 type SecretMaterialProviderRuntime = {
50− rows: SqlControlPlaneRows;
50+ rows: ControlPlaneStoreShape;
5151 env: NodeJS.ProcessEnv;
5252 dangerouslyAllowEnvSecrets: boolean;
5353 keychainServiceName: string;
@@ -106,8 +106,10 @@ export const parseSecretStoreProviderId = (value: string | undefined): SecretSto · 52 unmodified lines
106106 return KEYCHAIN_SECRET_PROVIDER_ID;
107107 }
108108
109− if (normalized === POSTGRES_SECRET_PROVIDER_ID) {
110− return POSTGRES_SECRET_PROVIDER_ID;
109+ if (
110+ normalized === LOCAL_SECRET_PROVIDER_ID
111+ ) {
112+ return LOCAL_SECRET_PROVIDER_ID;
111113 }
112114
113115 return null;
@@ -119,7 +121,7 @@ const resolveDangerouslyAllowEnvSecrets = (value: boolean | undefined): boolean · 5 unmodified lines
119121 const resolveSecretStoreProviderId = (value: SecretStoreProviderId | undefined): SecretStoreProviderId =>
120122 value
121123 ?? parseSecretStoreProviderId(process.env[SECRET_STORE_PROVIDER_ENV])
122− ?? POSTGRES_SECRET_PROVIDER_ID;
124+ ?? LOCAL_SECRET_PROVIDER_ID;
123125
124126 const resolveKeychainServiceName = (value: string | undefined): string =>
125127 trimOrNull(value)
@@ -239,7 +241,7 @@ const createEnvSecretMaterialProvider = (): SecretMaterialProvider => ({ · 113 unmodified lines
239241 remove: () => Effect.succeed(false),
240242 });
241243
242−const createPostgresSecretMaterialProvider = (): SecretMaterialProvider => ({
244+const createLocalSecretMaterialProvider = (): SecretMaterialProvider => ({
243245 resolve: ({ ref, runtime }) =>
244246 Effect.gen(function* () {
245247 const materialId = SecretMaterialIdSchema.make(ref.handle);
@@ -265,7 +267,7 @@ const createPostgresSecretMaterialProvider = (): SecretMaterialProvider => ({ · 19 unmodified lines
265267 });
266268
267269 return {
268− providerId: POSTGRES_SECRET_PROVIDER_ID,
270+ providerId: LOCAL_SECRET_PROVIDER_ID,
269271 handle: id,
270272 } satisfies SecretRef;
271273 }),
@@ -467,7 +469,7 @@ const createSecretMaterialProviderRegistry = (): SecretMaterialProviderRegistry · 195 unmodified lines
467469 [PARAMS_SECRET_PROVIDER_ID, createParamsSecretMaterialProvider()],
468470 [ENV_SECRET_PROVIDER_ID, createEnvSecretMaterialProvider()],
469471 [KEYCHAIN_SECRET_PROVIDER_ID, createKeychainSecretMaterialProvider()],
470− [POSTGRES_SECRET_PROVIDER_ID, createPostgresSecretMaterialProvider()],
472+ [LOCAL_SECRET_PROVIDER_ID, createLocalSecretMaterialProvider()],
471473 ]);
472474
473475 const getSecretMaterialProvider = (input: {
@@ -483,7 +485,7 @@ const getSecretMaterialProvider = (input: { · 9 unmodified lines
483485 };
484486
485487 const createSecretMaterialProviderRuntime = (input: {
486− rows: SqlControlPlaneRows;
488+ rows: ControlPlaneStoreShape;
487489 dangerouslyAllowEnvSecrets?: boolean;
488490 keychainServiceName?: string;
489491 localConfig?: LocalExecutorConfig | null;
@@ -644,7 +646,7 @@ const resolveConfiguredSecretProvider = (input: { · 154 unmodified lines
644646 };
645647
646648 export const createDefaultSecretMaterialResolver = (input: {
647− rows: SqlControlPlaneRows;
649+ rows: ControlPlaneStoreShape;
648650 dangerouslyAllowEnvSecrets?: boolean;
649651 keychainServiceName?: string;
650652 localConfig?: LocalExecutorConfig | null;
@@ -682,7 +684,7 @@ export const createDefaultSecretMaterialResolver = (input: { · 31 unmodified lines
682684 };
683685
684686 export const createDefaultSecretMaterialStorer = (input: {
685− rows: SqlControlPlaneRows;
687+ rows: ControlPlaneStoreShape;
686688 storeProviderId?: SecretStoreProviderId;
687689 dangerouslyAllowEnvSecrets?: boolean;
688690 keychainServiceName?: string;
@@ -714,7 +716,7 @@ export const createDefaultSecretMaterialStorer = (input: { · 25 unmodified lines
714716 };
715717
716718 export const createDefaultSecretMaterialDeleter = (input: {
717− rows: SqlControlPlaneRows;
719+ rows: ControlPlaneStoreShape;
718720 dangerouslyAllowEnvSecrets?: boolean;
719721 keychainServiceName?: string;
720722 }): DeleteSecretMaterial => {
packages/control-plane/src/runtime/source-adapters/graphql.ts+0−71
@@ -5,7 +5,6 @@ import { · 4 unmodified lines
55 makeToolInvokerFromTools,
66 typeSignatureFromSchemaJson,
77 } from "@executor/codemode-core";
8−import type { SqlControlPlaneRows } from "#persistence";
98 import type {
109 GraphqlToolManifest,
1110 } from "../graphql-tools";
@@ -41,7 +40,6 @@ import { · 29 unmodified lines
4140 import type {
4241 SourceAdapter,
4342 SourceAdapterMaterialization,
44− SourceAdapterRepairRevisionInput,
4543 } from "./types";
4644 import {
4745 ConnectHttpAuthSchema,
@@ -285,75 +283,6 @@ const primaryDocument = ( · 237 unmodified lines
285283 documents.find((document) => document.documentKind === "graphql_introspection")
286284 ?? null;
287285
288−export const rebuildGraphqlRecipeRevisionContent = (input: {
289− rows: SqlControlPlaneRows;
290− sourceRecord: SourceAdapterRepairRevisionInput["sourceRecord"];
291− revision: SourceAdapterRepairRevisionInput["revision"];
292− documents: readonly StoredSourceRecipeDocumentRecord[];
293−}): Effect.Effect<void, Error, never> =>
294− Effect.gen(function* () {
295− const manifest =
296− input.revision.manifestJson !== null
297− ? yield* parseJsonValue<GraphqlToolManifest>({
298− label: `GraphQL manifest for ${input.sourceRecord.id}`,
299− value: input.revision.manifestJson,
300− })
301− : null;
302− const document = primaryDocument(input.documents);
303− const resolvedManifest =
304− manifest
305− ?? (document !== null
306− ? yield* extractGraphqlManifest(
307− input.sourceRecord.name,
308− document.contentText,
309− )
310− : null);
311−
312− if (resolvedManifest === null) {
313− return yield* Effect.fail(
314− new Error(
315− `Missing GraphQL manifest and document for ${input.sourceRecord.id}`,
316− ),
317− );
318− }
319−
320− const now = Math.max(input.revision.updatedAt, input.sourceRecord.updatedAt);
321− const definitions = compileGraphqlToolDefinitions(resolvedManifest);
322− const schemaBundle = toGraphqlSchemaBundleRecord({
323− recipeRevisionId: input.revision.id,
324− manifest: resolvedManifest,
325− now,
326− });
327−
328− yield* input.rows.sourceRecipeRevisions.update(input.revision.id, {
329− manifestJson: JSON.stringify(resolvedManifest),
330− manifestHash: resolvedManifest.sourceHash,
331− updatedAt: now,
332− });
333− yield* input.rows.sourceRecipeOperations.replaceForRevision({
334− recipeRevisionId: input.revision.id,
335− operations: definitions.map((definition) =>
336− toGraphqlRecipeOperationRecord({
337− recipeRevisionId: input.revision.id,
338− definition,
339− manifest: resolvedManifest,
340− now,
341− }),
342− ),
343− });
344− yield* input.rows.sourceRecipeSchemaBundles.replaceForRevision({
345− recipeRevisionId: input.revision.id,
346− bundles: schemaBundle ? [schemaBundle] : [],
347− });
348−
349− if (input.sourceRecord.sourceHash !== resolvedManifest.sourceHash) {
350− yield* input.rows.sources.update(input.sourceRecord.workspaceId, input.sourceRecord.id, {
351− sourceHash: resolvedManifest.sourceHash,
352− updatedAt: now,
353− });
354− }
355− });
356−
357286 export const graphqlSourceAdapter: SourceAdapter = {
358287 key: "graphql",
359288 displayName: "GraphQL",
packages/control-plane/src/runtime/source-adapters/mcp.ts+0−19
@@ -9,7 +9,6 @@ import { · 8 unmodified lines
99 type McpToolManifest,
1010 type McpToolManifestEntry,
1111 } from "@executor/codemode-mcp";
12−import type { SqlControlPlaneRows } from "#persistence";
1312 import type {
1413 Source,
1514 SourceRecipeRevisionId,
@@ -25,7 +24,6 @@ import * as Schema from "effect/Schema"; · 9 unmodified lines
2524 import {
2625 contentHash,
2726 normalizeSearchText,
28− persistRecipeMaterialization,
2927 type SourceRecipeMaterialization,
3028 } from "../source-recipe-support";
3129 import { namespaceFromSourceName } from "../source-names";
@@ -170,23 +168,6 @@ const toMcpRecipeOperationRecord = (input: { · 138 unmodified lines
170168 updatedAt: input.now,
171169 });
172170
173−export const persistMcpRecipeRevisionFromManifestEntries = (input: {
174− rows: SqlControlPlaneRows;
175− source: Source;
176− manifestEntries: readonly McpToolManifestEntry[];
177−}): Effect.Effect<void, Error, never> =>
178− Effect.gen(function* () {
179− yield* persistRecipeMaterialization({
180− rows: input.rows,
181− source: input.source,
182− materialization: materializationFromMcpManifestEntries({
183− recipeRevisionId: "src_recipe_rev_materialization" as SourceRecipeRevisionId,
184− endpoint: input.source.endpoint,
185− manifestEntries: input.manifestEntries,
186− }),
187− });
188− });
189−
190171 export const materializationFromMcpManifestEntries = (input: {
191172 recipeRevisionId: SourceRecipeRevisionId;
192173 endpoint: string;
packages/control-plane/src/runtime/source-adapters/openapi.ts+0−70
@@ -3,7 +3,6 @@ import { · 2 unmodified lines
33 HttpClient,
44 HttpClientRequest,
55 } from "@effect/platform";
6−import type { SqlControlPlaneRows } from "#persistence";
76 import {
87 applyCookiePlacementsToHeaders,
98 applyHttpQueryPlacementsToUrl,
@@ -41,7 +40,6 @@ import { · 31 unmodified lines
4140 import type {
4241 SourceAdapter,
4342 SourceAdapterMaterialization,
44− SourceAdapterRepairRevisionInput,
4543 } from "./types";
4644 import {
4745 ConnectHttpAuthSchema,
@@ -330,74 +328,6 @@ const primaryDocument = ( · 282 unmodified lines
330328 ): StoredSourceRecipeDocumentRecord | null =>
331329 documents.find((document) => document.documentKind === "openapi") ?? null;
332330
333−export const rebuildOpenApiRecipeRevisionContent = (input: {
334− rows: SqlControlPlaneRows;
335− sourceRecord: SourceAdapterRepairRevisionInput["sourceRecord"];
336− revision: SourceAdapterRepairRevisionInput["revision"];
337− documents: readonly StoredSourceRecipeDocumentRecord[];
338−}): Effect.Effect<void, Error, never> =>
339− Effect.gen(function* () {
340− const manifest =
341− input.revision.manifestJson !== null
342− ? ((yield* parseJsonValue<OpenApiToolManifest>({
343− label: `OpenAPI manifest for ${input.sourceRecord.id}`,
344− value: input.revision.manifestJson,
345− })) as OpenApiToolManifest | null)
346− : null;
347− const document = primaryDocument(input.documents);
348− const resolvedManifest =
349− manifest
350− ?? (document !== null
351− ? yield* extractOpenApiManifest(
352− input.sourceRecord.name,
353− document.contentText,
354− )
355− : null);
356−
357− if (resolvedManifest === null) {
358− return yield* Effect.fail(
359− new Error(
360− `Missing OpenAPI manifest and document for ${input.sourceRecord.id}`,
361− ),
362− );
363− }
364−
365− const now = Math.max(input.revision.updatedAt, input.sourceRecord.updatedAt);
366− const definitions = compileOpenApiToolDefinitions(resolvedManifest);
367− const schemaBundle = toOpenApiSchemaBundleRecord({
368− recipeRevisionId: input.revision.id,
369− refTable: resolvedManifest.refHintTable,
370− now,
371− });
372−
373− yield* input.rows.sourceRecipeRevisions.update(input.revision.id, {
374− manifestJson: JSON.stringify(resolvedManifest),
375− manifestHash: resolvedManifest.sourceHash,
376− updatedAt: now,
377− });
378− yield* input.rows.sourceRecipeOperations.replaceForRevision({
379− recipeRevisionId: input.revision.id,
380− operations: definitions.map((definition) =>
381− toOpenApiRecipeOperationRecord({
382− recipeRevisionId: input.revision.id,
383− definition,
384− now,
385− }),
386− ),
387− });
388− yield* input.rows.sourceRecipeSchemaBundles.replaceForRevision({
389− recipeRevisionId: input.revision.id,
390− bundles: schemaBundle ? [schemaBundle] : [],
391− });
392−
393− if (input.sourceRecord.sourceHash !== resolvedManifest.sourceHash) {
394− yield* input.rows.sources.update(input.sourceRecord.workspaceId, input.sourceRecord.id, {
395− sourceHash: resolvedManifest.sourceHash,
396− updatedAt: now,
397− });
398− }
399− });
400−
401331 export const openApiSourceAdapter: SourceAdapter = {
402332 key: "openapi",
403333 displayName: "OpenAPI",
packages/control-plane/src/runtime/source-adapters/types.ts+2−11
@@ -3,7 +3,6 @@ import type { · 2 unmodified lines
33 ToolDescriptor,
44 ToolSchemaBundle,
55 } from "@executor/codemode-core";
6−import type { SqlControlPlaneRows } from "#persistence";
76 import type {
87 AccountId,
98 CredentialSlot,
@@ -25,6 +24,7 @@ import * as Schema from "effect/Schema"; · 15 unmodified lines
2524
2625 import type { ResolvedSourceAuthMaterial } from "../source-auth-material";
2726 import type { ResolveSecretMaterial } from "../secret-material-providers";
27+import type { ControlPlaneStoreShape } from "../store";
2828
2929 export type SourceAdapterFamily = "http_api" | "mcp" | "internal";
3030 export type SourceAdapterInputSchema = Schema.Schema<any, any, never>;
@@ -81,17 +81,8 @@ export type SourceAdapterOauth2SetupConfig = { · 50 unmodified lines
8181 authorizationParams?: Readonly<Record<string, string>>;
8282 };
8383
84−export type SourceAdapterRepairRevisionInput = {
85− rows: SqlControlPlaneRows;
86− sourceRecord: StoredSourceRecord;
87− revision: StoredSourceRecipeRevisionRecord;
88− documents: readonly StoredSourceRecipeDocumentRecord[];
89− operations: readonly StoredSourceRecipeOperationRecord[];
90− schemaBundles: readonly ToolSchemaBundle[];
91−};
92−
9384 export type SourceAdapterInvokePersistedToolInput = {
94− rows: SqlControlPlaneRows;
85+ rows: ControlPlaneStoreShape;
9586 workspaceId: Source["workspaceId"];
9687 accountId: AccountId;
9788 source: Source;
packages/control-plane/src/runtime/source-auth-material.test.ts+5−5
@@ -64,7 +64,7 @@ describe("source-auth-material", () => { · 63 unmodified lines
6464 headerName: "X-Api-Key",
6565 prefix: "Token ",
6666 token: {
67− providerId: "postgres",
67+ providerId: "local",
6868 handle: "sec_bearer",
6969 },
7070 },
@@ -75,7 +75,7 @@ describe("source-auth-material", () => { · 4 unmodified lines
7575 },
7676 }));
7777
78− expect(calls).toEqual(["postgres:sec_bearer"]);
78+ expect(calls).toEqual(["local:sec_bearer"]);
7979 expect(auth).toEqual({
8080 placements: [
8181 {
@@ -110,11 +110,11 @@ describe("source-auth-material", () => { · 28 unmodified lines
110110 headerName: "Authorization",
111111 prefix: "Bearer ",
112112 accessToken: {
113− providerId: "postgres",
113+ providerId: "local",
114114 handle: "sec_access",
115115 },
116116 refreshToken: {
117− providerId: "postgres",
117+ providerId: "local",
118118 handle: "sec_refresh",
119119 },
120120 },
@@ -125,7 +125,7 @@ describe("source-auth-material", () => { · 4 unmodified lines
125125 },
126126 }));
127127
128− expect(calls).toEqual(["postgres:sec_access"]);
128+ expect(calls).toEqual(["local:sec_access"]);
129129 expect(auth).toEqual({
130130 placements: [
131131 {
packages/control-plane/src/runtime/source-auth-material.ts+2−2
@@ -1,4 +1,3 @@
1−import type { SqlControlPlaneRows } from "#persistence";
21 import type {
32 AccountId,
43 CredentialSlot,
@@ -8,6 +7,7 @@ import type { · 3 unmodified lines
87 import * as Effect from "effect/Effect";
98 import * as Option from "effect/Option";
109
10+import type { ControlPlaneStoreShape } from "./store";
1111 import {
1212 authArtifactFromSourceAuth,
1313 resolveAuthArtifactMaterial,
@@ -42,7 +42,7 @@ export const resolveSourceAuthMaterial = (input: { · 28 unmodified lines
4242 source: Source;
4343 slot?: CredentialSlot;
4444 actorAccountId?: AccountId | null;
45− rows?: SqlControlPlaneRows;
45+ rows?: ControlPlaneStoreShape;
4646 resolveSecretMaterial: ResolveSecretMaterial;
4747 context?: SecretMaterialResolveContext;
4848 }): Effect.Effect<ResolvedSourceAuthMaterial, Error, never> =>
packages/control-plane/src/runtime/source-auth-service.test.ts+0−608
@@ -1,608 +0,0 @@
1−import { mkdtempSync } from "node:fs";
2−import { createServer } from "node:http";
3−import { tmpdir } from "node:os";
4−import { join } from "node:path";
5−
6−import { describe, expect, it } from "vitest";
7−import * as Schema from "effect/Schema";
8−import * as Effect from "effect/Effect";
9−import * as Option from "effect/Option";
10−
11−import {
12− AccountIdSchema,
13− McpSourceAuthSessionDataJsonSchema,
14− OAuth2PkceSourceAuthSessionDataJsonSchema,
15− SecretMaterialIdSchema,
16− type Source,
17− type SourceAuthSession,
18− WorkspaceIdSchema,
19− decodeAuthLeasePlacementTemplates,
20− decodeBuiltInAuthArtifactConfig,
21−} from "#schema";
22−import {
23− createSqlControlPlanePersistence,
24− type SqlControlPlanePersistence,
25−} from "#persistence";
26−
27−import {
28− createRuntimeSourceAuthService,
29− createTerminalSourceAuthSessionPatch,
30−} from "./source-auth-service";
31−import { createLiveExecutionManager } from "./live-execution";
32−import {
33− loadLocalExecutorConfig,
34− resolveLocalWorkspaceContext,
35−} from "./local-config";
36−import {
37− readLocalSourceArtifact,
38−} from "./local-source-artifacts";
39−import {
40− RuntimeLocalWorkspaceService,
41− type RuntimeLocalWorkspaceState,
42−} from "./local-runtime-context";
43−import { createDefaultSecretMaterialResolver } from "./secret-material-providers";
44−import { resolveSourceAuthMaterial } from "./source-auth-material";
45−
46−const makeExistingOpenApiSource = (auth: Source["auth"]): Source => ({
47− id: "src_test" as Source["id"],
48− workspaceId: "ws_test" as Source["workspaceId"],
49− name: "GitHub",
50− kind: "openapi",
51− endpoint: "https://api.github.com",
52− status: "connected",
53− enabled: true,
54− namespace: "github",
55− bindingVersion: 1,
56− binding: {
57− specUrl: "https://example.com/openapi.json",
58− defaultHeaders: null,
59− },
60− importAuthPolicy: "reuse_runtime",
61− importAuth: { kind: "none" },
62− auth,
63− sourceHash: null,
64− lastError: null,
65− createdAt: 1,
66− updatedAt: 1,
67−});
68−
69−const fetchLiveDiscoveryDocument = async (): Promise<string> => {
70− const response = await fetch("https://www.googleapis.com/discovery/v1/apis/sheets/v4/rest", {
71− signal: AbortSignal.timeout(20_000),
72− });
73− if (!response.ok) {
74− throw new Error(`Failed to fetch Sheets discovery doc: ${response.status}`);
75− }
76−
77− return response.text();
78−};
79−
80−const withGoogleAuthTestServer = async <T>(input: {
81− discoveryDocument: string;
82− handler: (server: {
83− discoveryUrl: string;
84− tokenUrl: string;
85− tokenRequests: Array<URLSearchParams>;
86− }) => Promise<T>;
87−}): Promise<T> => {
88− const tokenRequests: Array<URLSearchParams> = [];
89− const server = createServer(async (request, response) => {
90− const requestUrl = new URL(
91− request.url ?? "/",
92− "http://127.0.0.1",
93− );
94−
95− if (request.method === "GET" && requestUrl.pathname === "/discovery") {
96− response.statusCode = 200;
97− response.setHeader("content-type", "application/json");
98− response.end(input.discoveryDocument);
99− return;
100− }
101−
102− if (request.method === "POST" && requestUrl.pathname === "/token") {
103− const chunks: Buffer[] = [];
104− for await (const chunk of request) {
105− chunks.push(Buffer.from(chunk));
106− }
107− const form = new URLSearchParams(Buffer.concat(chunks).toString("utf8"));
108− tokenRequests.push(form);
109−
110− response.statusCode = 200;
111− response.setHeader("content-type", "application/json");
112− if (form.get("grant_type") === "authorization_code") {
113− response.end(
114− JSON.stringify({
115− access_token: "access-token-initial",
116− refresh_token: "refresh-token-live",
117− expires_in: 3600,
118− scope: "https://www.googleapis.com/auth/spreadsheets.readonly",
119− token_type: "Bearer",
120− }),
121− );
122− return;
123− }
124−
125− response.end(
126− JSON.stringify({
127− access_token: "access-token-refreshed",
128− expires_in: 3600,
129− token_type: "Bearer",
130− }),
131− );
132− return;
133− }
134−
135− response.statusCode = 404;
136− response.end();
137− });
138−
139− await new Promise<void>((resolve, reject) => {
140− server.listen(0, "127.0.0.1", (error?: Error) => {
141− if (error) {
142− reject(error);
143− return;
144− }
145− resolve();
146− });
147− });
148−
149− try {
150− const address = server.address();
151− if (!address || typeof address === "string") {
152− throw new Error("Failed to resolve Google auth test server address");
153− }
154− const baseUrl = `http://127.0.0.1:${address.port}`;
155− return await input.handler({
156− discoveryUrl: `${baseUrl}/discovery`,
157− tokenUrl: `${baseUrl}/token`,
158− tokenRequests,
159− });
160− } finally {
161− await new Promise<void>((resolve, reject) => {
162− server.close((error) => {
163− if (error) {
164− reject(error);
165− return;
166− }
167− resolve();
168− });
169− });
170− }
171−};
172−
173−const makePersistence = async (): Promise<SqlControlPlanePersistence> =>
174− Effect.runPromise(
175− createSqlControlPlanePersistence({
176− localDataDir: ":memory:",
177− }),
178− );
179−
180−const makeRuntimeLocalWorkspaceState = async (
181− workspaceId: ReturnType<typeof WorkspaceIdSchema.make>,
182− accountId: ReturnType<typeof AccountIdSchema.make>,
183−): Promise<RuntimeLocalWorkspaceState> =>
184− Effect.runPromise(
185− Effect.gen(function* () {
186− const workspaceRoot = mkdtempSync(
187− join(tmpdir(), "executor-source-auth-service-"),
188− );
189− const context = yield* resolveLocalWorkspaceContext({ workspaceRoot });
190− const loadedConfig = yield* loadLocalExecutorConfig(context);
191−
192− return {
193− context,
194− installation: {
195− workspaceId,
196− accountId,
197− },
198− loadedConfig,
199− } satisfies RuntimeLocalWorkspaceState;
200− }),
201− );
202−
203−describe("source-auth-service", () => {
204− const encodeSessionDataJson = Schema.encodeSync(McpSourceAuthSessionDataJsonSchema);
205−
206− const baseSessionDataJson = encodeSessionDataJson({
207− kind: "mcp_oauth",
208− endpoint: "https://example.com/resource",
209− redirectUri: "http://127.0.0.1/callback",
210− scope: null,
211− resourceMetadataUrl: "https://example.com/resource",
212− authorizationServerUrl: "https://example.com/as",
213− resourceMetadata: {
214− issuer: "https://example.com",
215− },
216− authorizationServerMetadata: {
217− token_endpoint: "https://example.com/token",
218− },
219− clientInformation: {
220− client_id: "abc",
221− },
222− codeVerifier: "verifier",
223− authorizationUrl: "https://example.com/auth",
224− });
225−
226− it("clears ephemeral OAuth session fields when failing a session", () => {
227− const patch = createTerminalSourceAuthSessionPatch({
228− sessionDataJson: baseSessionDataJson,
229− status: "failed",
230− now: 123,
231− errorText: "OAuth authorization failed",
232− });
233−
234− expect(patch).toMatchObject({
235− sessionDataJson: baseSessionDataJson,
236− status: "failed",
237− errorText: "OAuth authorization failed",
238− completedAt: 123,
239− updatedAt: 123,
240− });
241− });
242−
243− it("clears ephemeral OAuth session fields when completing a session", () => {
244− const patch = createTerminalSourceAuthSessionPatch({
245− sessionDataJson: baseSessionDataJson,
246− status: "completed",
247− now: 456,
248− errorText: null,
249− });
250−
251− expect(patch).toMatchObject({
252− sessionDataJson: baseSessionDataJson,
253− status: "completed",
254− errorText: null,
255− completedAt: 456,
256− updatedAt: 456,
257− });
258− });
259−
260− it("completes Google Discovery desktop OAuth into a refreshable auth artifact and lease", async () => {
261− const discoveryDocument = await fetchLiveDiscoveryDocument();
262−
263− const persistence = await makePersistence();
264− try {
265− const workspaceId = WorkspaceIdSchema.make("ws_google_oauth");
266− const accountId = AccountIdSchema.make("acc_google_oauth");
267− const runtimeLocalWorkspace = await makeRuntimeLocalWorkspaceState(
268− workspaceId,
269− accountId,
270− );
271−
272− const service = createRuntimeSourceAuthService({
273− rows: persistence.rows,
274− liveExecutionManager: createLiveExecutionManager(),
275− getLocalServerBaseUrl: () => "http://localhost:8788",
276− localConfig: runtimeLocalWorkspace.loadedConfig.config,
277− workspaceRoot: runtimeLocalWorkspace.context.workspaceRoot,
278− localWorkspaceState: runtimeLocalWorkspace,
279− });
280−
281− await withGoogleAuthTestServer({
282− discoveryDocument,
283− handler: async ({ discoveryUrl, tokenUrl, tokenRequests }) => {
284− const originalFetch = globalThis.fetch;
285− globalThis.fetch = (async (input, init) => {
286− const url = typeof input === "string"
287− ? input
288− : input instanceof URL
289− ? input.toString()
290− : input.url;
291− if (url === "https://oauth2.googleapis.com/token") {
292− return originalFetch(tokenUrl, init);
293− }
294−
295− return originalFetch(input as any, init);
296− }) as typeof fetch;
297−
298− try {
299− const addResult = await Effect.runPromise(
300− service.addExecutorSource({
301− kind: "google_discovery",
302− workspaceId,
303− actorAccountId: accountId,
304− executionId: null,
305− interactionId: null,
306− service: "sheets",
307− version: "v4",
308− discoveryUrl,
309− scopes: ["https://www.googleapis.com/auth/spreadsheets.readonly"],
310− oauthClient: {
311− clientId: "google-test-client",
312− clientSecret: "google-test-secret",
313− },
314− }).pipe(
315− Effect.provideService(
316− RuntimeLocalWorkspaceService,
317− runtimeLocalWorkspace,
318− ),
319− ),
320− );
321−
322− expect(addResult.kind).toBe("oauth_required");
323− if (addResult.kind !== "oauth_required") {
324− return;
325− }
326−
327− const sessionOption = await Effect.runPromise(
328− persistence.rows.sourceAuthSessions.getById(addResult.sessionId),
329− );
330− expect(Option.isSome(sessionOption)).toBe(true);
331− const session = Option.getOrNull(sessionOption) as SourceAuthSession;
332− const oauthClientOption = await Effect.runPromise(
333− persistence.rows.sourceOauthClients.getByWorkspaceSourceAndProvider({
334− workspaceId,
335− sourceId: addResult.source.id,
336− providerKey: "google_workspace",
337− }),
338− );
339− expect(Option.isSome(oauthClientOption)).toBe(true);
340− const oauthClient = Option.getOrNull(oauthClientOption)!;
341− expect(oauthClient.clientId).toBe("google-test-client");
342− expect(oauthClient.clientSecretProviderId).toBe("postgres");
343− expect(oauthClient.clientSecretHandle).toBeTruthy();
344− expect(session.providerKind).toBe("oauth2_pkce");
345− const sessionData = Schema.decodeUnknownSync(
346− OAuth2PkceSourceAuthSessionDataJsonSchema,
347− )(session.sessionDataJson);
348− expect(sessionData.redirectUri).toMatch(/^http:\/\/127\.0\.0\.1:\d+$/);
349− expect(sessionData.tokenEndpoint).toBe("https://oauth2.googleapis.com/token");
350− expect(sessionData.authorizationUrl).toContain(
351− encodeURIComponent(sessionData.redirectUri),
352− );
353−
354− const loopbackResponse = await fetch(
355− `${sessionData.redirectUri}/?state=${encodeURIComponent(session.state)}&code=loopback-code`,
356− {
357− redirect: "manual",
358− },
359− );
360− expect(loopbackResponse.status).toBe(302);
361− const completionLocation = loopbackResponse.headers.get("location");
362− expect(completionLocation).toBeTruthy();
363− if (!completionLocation) {
364− return;
365− }
366− const completionUrl = new URL(completionLocation);
367− expect(completionUrl.origin).toBe("http://localhost:8788");
368− expect(completionUrl.searchParams.get("state")).toBe(session.state);
369− expect(completionUrl.searchParams.get("code")).toBe("loopback-code");
370−
371− const connectedSource = await Effect.runPromise(
372− service.completeSourceCredentialSetup({
373− workspaceId,
374− sourceId: addResult.source.id,
375− actorAccountId: accountId,
376− state: session.state,
377− code: "authorization-code",
378− }),
379− );
380−
381− expect(connectedSource.status).toBe("connected");
382− expect(connectedSource.auth.kind).toBe("oauth2_authorized_user");
383− if (connectedSource.auth.kind !== "oauth2_authorized_user") {
384− return;
385− }
386− expect(connectedSource.auth.clientId).toBe("google-test-client");
387− expect(connectedSource.auth.tokenEndpoint).toBe("https://oauth2.googleapis.com/token");
388− expect(connectedSource.auth.grantSet).toEqual([
389− "https://www.googleapis.com/auth/spreadsheets.readonly",
390− ]);
391−
392− expect(tokenRequests).toHaveLength(1);
393− expect(tokenRequests[0]?.get("grant_type")).toBe("authorization_code");
394− expect(tokenRequests[0]?.get("client_id")).toBe("google-test-client");
395− expect(tokenRequests[0]?.get("client_secret")).toBe("google-test-secret");
396− expect(tokenRequests[0]?.get("code")).toBe("authorization-code");
397− expect(tokenRequests[0]?.get("redirect_uri")).toBe(sessionData.redirectUri);
398− expect(tokenRequests[0]?.get("code_verifier")).toBe(sessionData.codeVerifier);
399−
400− const artifactOption = await Effect.runPromise(
401− persistence.rows.authArtifacts.getByWorkspaceSourceAndActor({
402− workspaceId,
403− sourceId: connectedSource.id,
404− actorAccountId: accountId,
405− slot: "runtime",
406− }),
407− );
408− expect(Option.isSome(artifactOption)).toBe(true);
409− const artifact = Option.getOrNull(artifactOption)!;
410− const decodedArtifact = decodeBuiltInAuthArtifactConfig(artifact);
411− expect(decodedArtifact?.artifactKind).toBe("oauth2_authorized_user");
412−
413− const leaseOption = await Effect.runPromise(
414− persistence.rows.authLeases.getByAuthArtifactId(artifact.id),
415− );
416− expect(Option.isSome(leaseOption)).toBe(true);
417− const initialLease = Option.getOrNull(leaseOption)!;
418− const initialTemplates = decodeAuthLeasePlacementTemplates(initialLease);
419− expect(initialTemplates).toEqual([
420− {
421− location: "header",
422− name: "Authorization",
423− parts: [
424− {
425− kind: "literal",
426− value: "Bearer ",
427− },
428− {
429− kind: "secret_ref",
430− ref: {
431− providerId: "postgres",
432− handle: expect.any(String),
433− },
434− },
435− ],
436− },
437− ]);
438−
439− const resolver = createDefaultSecretMaterialResolver({
440− rows: persistence.rows,
441− });
442− const resolvedInitialAuth = await Effect.runPromise(
443− resolveSourceAuthMaterial({
444− source: connectedSource,
445− slot: "runtime",
446− actorAccountId: accountId,
447− rows: persistence.rows,
448− resolveSecretMaterial: resolver,
449− }),
450− );
451− expect(resolvedInitialAuth.headers.Authorization).toBe(
452− "Bearer access-token-initial",
453− );
454−
455− await Effect.runPromise(
456− persistence.rows.authLeases.upsert({
457− ...initialLease,
458− refreshAfter: Date.now() - 1,
459− expiresAt: Date.now() + 10_000,
460− updatedAt: Date.now(),
461− }),
462− );
463−
464− const previousAccessTokenHandle = initialTemplates?.[0]?.parts[1];
465− const refreshedAuth = await Effect.runPromise(
466− resolveSourceAuthMaterial({
467− source: connectedSource,
468− slot: "runtime",
469− actorAccountId: accountId,
470− rows: persistence.rows,
471− resolveSecretMaterial: resolver,
472− }),
473− );
474− expect(refreshedAuth.headers.Authorization).toBe(
475− "Bearer access-token-refreshed",
476− );
477− expect(tokenRequests).toHaveLength(2);
478− expect(tokenRequests[1]?.get("grant_type")).toBe("refresh_token");
479− expect(tokenRequests[1]?.get("refresh_token")).toBe("refresh-token-live");
480−
481− if (previousAccessTokenHandle?.kind === "secret_ref") {
482− const oldSecret = await Effect.runPromise(
483− persistence.rows.secretMaterials.getById(
484− SecretMaterialIdSchema.make(previousAccessTokenHandle.ref.handle),
485− ),
486− );
487− expect(Option.isNone(oldSecret)).toBe(true);
488− }
489−
490− const localArtifact = await Effect.runPromise(readLocalSourceArtifact({
491− context: runtimeLocalWorkspace.context,
492− sourceId: connectedSource.id,
493− }));
494− expect(localArtifact).not.toBeNull();
495− expect(localArtifact?.operations.length).toBeGreaterThan(0);
496− expect(
497− localArtifact?.operations.some(
498− (operation) => operation.toolId === "spreadsheets.values.get",
499− ),
500− ).toBe(true);
501− } finally {
502− globalThis.fetch = originalFetch;
503− }
504− },
505− });
506− } finally {
507− await persistence.close();
508− }
509− }, 60_000);
510−
511− it("uses the request origin callback for Google Discovery OAuth started from the web app", async () => {
512− const discoveryDocument = await fetchLiveDiscoveryDocument();
513−
514− const persistence = await makePersistence();
515− try {
516− const workspaceId = WorkspaceIdSchema.make("ws_google_oauth_web");
517− const accountId = AccountIdSchema.make("acc_google_oauth_web");
518− const runtimeLocalWorkspace = await makeRuntimeLocalWorkspaceState(
519− workspaceId,
520− accountId,
521− );
522−
523− const service = createRuntimeSourceAuthService({
524− rows: persistence.rows,
525− liveExecutionManager: createLiveExecutionManager(),
526− getLocalServerBaseUrl: () => "http://127.0.0.1:8788",
527− localConfig: runtimeLocalWorkspace.loadedConfig.config,
528− workspaceRoot: runtimeLocalWorkspace.context.workspaceRoot,
529− localWorkspaceState: runtimeLocalWorkspace,
530− });
531−
532− await withGoogleAuthTestServer({
533− discoveryDocument,
534− handler: async ({ discoveryUrl, tokenUrl }) => {
535− const originalFetch = globalThis.fetch;
536− globalThis.fetch = (async (input, init) => {
537− const url = typeof input === "string"
538− ? input
539− : input instanceof URL
540− ? input.toString()
541− : input.url;
542− if (url === "https://oauth2.googleapis.com/token") {
543− return originalFetch(tokenUrl, init);
544− }
545−
546− return originalFetch(input as any, init);
547− }) as typeof fetch;
548−
549− try {
550− const addResult = await Effect.runPromise(
551− service.addExecutorSource(
552− {
553− kind: "google_discovery",
554− workspaceId,
555− actorAccountId: accountId,
556− executionId: null,
557− interactionId: null,
558− service: "sheets",
559− version: "v4",
560− discoveryUrl,
561− scopes: ["https://www.googleapis.com/auth/spreadsheets.readonly"],
562− oauthClient: {
563− clientId: "google-test-client",
564− clientSecret: "google-test-secret",
565− },
566− },
567− {
568− baseUrl: "https://app.executor.dev",
569− },
570− ).pipe(
571− Effect.provideService(
572− RuntimeLocalWorkspaceService,
573− runtimeLocalWorkspace,
574− ),
575− ),
576− );
577−
578− expect(addResult.kind).toBe("oauth_required");
579− if (addResult.kind !== "oauth_required") {
580− return;
581− }
582−
583− const sessionOption = await Effect.runPromise(
584− persistence.rows.sourceAuthSessions.getById(addResult.sessionId),
585− );
586− expect(Option.isSome(sessionOption)).toBe(true);
587− const session = Option.getOrNull(sessionOption) as SourceAuthSession;
588− const sessionData = Schema.decodeUnknownSync(
589− OAuth2PkceSourceAuthSessionDataJsonSchema,
590− )(session.sessionDataJson);
591−
592− expect(sessionData.redirectUri).toBe(
593− `https://app.executor.dev/v1/workspaces/${encodeURIComponent(workspaceId)}/sources/${encodeURIComponent(addResult.source.id)}/credentials/oauth/complete`,
594− );
595− expect(sessionData.authorizationUrl).toContain(
596− encodeURIComponent(sessionData.redirectUri),
597− );
598− expect(sessionData.redirectUri).not.toMatch(/^http:\/\/127\.0\.0\.1:\d+$/);
599− } finally {
600− globalThis.fetch = originalFetch;
601− }
602− },
603− });
604− } finally {
605− await persistence.close();
606− }
607− }, 60_000);
608−});
packages/control-plane/src/runtime/source-auth-service.ts+27−25
@@ -3,10 +3,6 @@ import { · 2 unmodified lines
33 discoverMcpToolsFromConnector,
44 type McpDiscoveryElicitationContext,
55 } from "@executor/codemode-mcp";
6−import {
7− SqlControlPlaneRowsService,
8− type SqlControlPlaneRows,
9−} from "#persistence";
106 import {
117 AccountId,
128 type CredentialSlot,
@@ -86,6 +82,8 @@ import { · 73 unmodified lines
8682 loadSourcesInWorkspace,
8783 persistSource,
8884 } from "./source-store";
85+import type { WorkspaceStorageServices } from "./local-storage";
86+import { ControlPlaneStore, type ControlPlaneStoreShape } from "./store";
8987
9088 const trimOrNull = (value: string | null | undefined): string | null => {
9189 if (value === null || value === undefined) {
@@ -303,7 +301,7 @@ const mergeOauth2PkceSourceAuthSessionData = (input: { · 211 unmodified lines
303301
304302
305303 const completeLiveInteraction = (input: {
306− rows: SqlControlPlaneRows;
304+ rows: ControlPlaneStoreShape;
307305 liveExecutionManager: LiveExecutionManager;
308306 session: SourceAuthSession;
309307 response: {
@@ -360,7 +358,7 @@ const serializeJson = (value: unknown): string | null => { · 50 unmodified lines
360358 return JSON.stringify(value);
361359 };
362360
363−const updateSourceStatus = (rows: SqlControlPlaneRows, source: Source, input: {
361+const updateSourceStatus = (rows: ControlPlaneStoreShape, source: Source, input: {
364362 actorAccountId?: AccountId | null;
365363 status: Source["status"];
366364 lastError?: string | null;
@@ -743,7 +741,7 @@ const sourceOauthClientSecretRef = (client: { · 376 unmodified lines
743741 : null;
744742
745743 const upsertSourceOauthClient = (input: {
746− rows: SqlControlPlaneRows;
744+ rows: ControlPlaneStoreShape;
747745 source: Source;
748746 oauthClient: SourceOauthClientInput;
749747 storeSecretMaterial: StoreSecretMaterial;
@@ -825,7 +823,7 @@ const upsertSourceOauthClient = (input: { · 75 unmodified lines
825823 });
826824
827825 const resolveExistingSourceOauthClient = (input: {
828− rows: SqlControlPlaneRows;
826+ rows: ControlPlaneStoreShape;
829827 source: Source;
830828 }): Effect.Effect<ResolvedSourceOauthClient | null, Error, never> =>
831829 Effect.gen(function* () {
@@ -858,7 +856,7 @@ const resolveExistingSourceOauthClient = (input: { · 26 unmodified lines
858856 });
859857
860858 const startOauth2PkceSourceCredentialSetup = (input: {
861− rows: SqlControlPlaneRows;
859+ rows: ControlPlaneStoreShape;
862860 source: Source;
863861 actorAccountId?: AccountId | null;
864862 executionId?: SourceAuthSession["executionId"];
@@ -866,7 +864,11 @@ const startOauth2PkceSourceCredentialSetup = (input: { · 1 unmodified line
866864 baseUrl: string;
867865 redirectModeOverride?: WorkspaceSourceOauthClientRedirectMode;
868866 storeSecretMaterial: StoreSecretMaterial;
869−}): Effect.Effect<Extract<ExecutorSourceAddResult, { kind: "oauth_required" }> | null, Error, never> =>
867+}): Effect.Effect<
868+ Extract<ExecutorSourceAddResult, { kind: "oauth_required" }> | null,
869+ Error,
870+ WorkspaceStorageServices
871+> =>
870872 Effect.gen(function* () {
871873 const adapter = getSourceAdapterForSource(input.source);
872874 const setupConfig = adapter.getOauth2SetupConfig
@@ -971,7 +973,7 @@ const startOauth2PkceSourceCredentialSetup = (input: { · 98 unmodified lines
971973 });
972974
973975 const connectMcpSourceInternal = (input: {
974− rows: SqlControlPlaneRows;
976+ rows: ControlPlaneStoreShape;
975977 getLocalServerBaseUrl?: () => string | undefined;
976978 baseUrl?: string | null;
977979 workspaceId: WorkspaceId;
@@ -988,7 +990,7 @@ const connectMcpSourceInternal = (input: { · 10 unmodified lines
988990 headers?: StringMap | null;
989991 mcpDiscoveryElicitation?: McpDiscoveryElicitationContext;
990992 resolveSecretMaterial: ResolveSecretMaterial;
991−}): Effect.Effect<McpSourceConnectResult, Error, never> =>
993+}): Effect.Effect<McpSourceConnectResult, Error, WorkspaceStorageServices> =>
992994 Effect.gen(function* () {
993995 const normalizedEndpoint = normalizeEndpoint(input.endpoint);
994996 const existing = yield* (
@@ -1200,13 +1202,13 @@ const connectMcpSourceInternal = (input: { · 205 unmodified lines
12001202 });
12011203
12021204 const addExecutorHttpSource = (input: {
1203− rows: SqlControlPlaneRows;
1205+ rows: ControlPlaneStoreShape;
12041206 sourceInput: Extract<ExecutorAddSourceInput, { kind: "openapi" | "graphql" }>;
12051207 storeSecretMaterial: StoreSecretMaterial;
12061208 resolveSecretMaterial: ResolveSecretMaterial;
12071209 getLocalServerBaseUrl?: () => string | undefined;
12081210 baseUrl?: string | null;
1209−}): Effect.Effect<ExecutorSourceAddResult, Error, never> =>
1211+}): Effect.Effect<ExecutorSourceAddResult, Error, WorkspaceStorageServices> =>
12101212 Effect.gen(function* () {
12111213 const normalizedEndpoint = normalizeEndpoint(input.sourceInput.endpoint);
12121214 const normalizedSpecUrl = input.sourceInput.kind === "openapi"
@@ -1403,13 +1405,13 @@ const addExecutorHttpSource = (input: { · 190 unmodified lines
14031405 });
14041406
14051407 const addExecutorGoogleDiscoverySource = (input: {
1406− rows: SqlControlPlaneRows;
1408+ rows: ControlPlaneStoreShape;
14071409 sourceInput: Extract<ExecutorAddSourceInput, { kind: "google_discovery" }>;
14081410 storeSecretMaterial: StoreSecretMaterial;
14091411 resolveSecretMaterial: ResolveSecretMaterial;
14101412 getLocalServerBaseUrl?: () => string | undefined;
14111413 baseUrl?: string | null;
1412−}): Effect.Effect<ExecutorSourceAddResult, Error, never> =>
1414+}): Effect.Effect<ExecutorSourceAddResult, Error, WorkspaceStorageServices> =>
14131415 Effect.gen(function* () {
14141416 const normalizedService = input.sourceInput.service.trim();
14151417 const normalizedVersion = input.sourceInput.version.trim();
@@ -1627,7 +1629,7 @@ type RuntimeSourceAuthServiceShape = { · 211 unmodified lines
16271629 workspaceId: WorkspaceId;
16281630 sourceId: Source["id"];
16291631 actorAccountId?: AccountId | null;
1630− }) => Effect.Effect<Source, Error, never>;
1632+ }) => Effect.Effect<Source, Error, WorkspaceStorageServices>;
16311633 getLocalServerBaseUrl: () => string | null;
16321634 storeSecretMaterial: (input: {
16331635 purpose: SecretMaterialPurpose;
@@ -1639,10 +1641,10 @@ type RuntimeSourceAuthServiceShape = { · 5 unmodified lines
16391641 mcpDiscoveryElicitation?: McpDiscoveryElicitationContext;
16401642 baseUrl?: string | null;
16411643 },
1642− ) => Effect.Effect<ExecutorSourceAddResult, Error, never>;
1644+ ) => Effect.Effect<ExecutorSourceAddResult, Error, WorkspaceStorageServices>;
16431645 connectMcpSource: (
16441646 input: ConnectMcpSourceInput,
1645− ) => Effect.Effect<McpSourceConnectResult, Error, never>;
1647+ ) => Effect.Effect<McpSourceConnectResult, Error, WorkspaceStorageServices>;
16461648 startSourceOAuthSession: (
16471649 input: StartSourceOAuthSessionInput,
16481650 ) => Effect.Effect<StartSourceOAuthSessionResult, Error, never>;
@@ -1651,7 +1653,7 @@ type RuntimeSourceAuthServiceShape = { · 2 unmodified lines
16511653 code?: string | null;
16521654 error?: string | null;
16531655 errorDescription?: string | null;
1654− }) => Effect.Effect<CompleteSourceOAuthSessionResult, Error, never>;
1656+ }) => Effect.Effect<CompleteSourceOAuthSessionResult, Error, WorkspaceStorageServices>;
16551657 completeSourceCredentialSetup: (input: {
16561658 workspaceId: WorkspaceId;
16571659 sourceId: Source["id"];
@@ -1660,11 +1662,11 @@ type RuntimeSourceAuthServiceShape = { · 2 unmodified lines
16601662 code?: string | null;
16611663 error?: string | null;
16621664 errorDescription?: string | null;
1663− }) => Effect.Effect<Source, Error, never>;
1665+ }) => Effect.Effect<Source, Error, WorkspaceStorageServices>;
16641666 };
16651667
16661668 export const createRuntimeSourceAuthService = (input: {
1667− rows: SqlControlPlaneRows;
1669+ rows: ControlPlaneStoreShape;
16681670 liveExecutionManager: LiveExecutionManager;
16691671 getLocalServerBaseUrl?: () => string | undefined;
16701672 localConfig?: LocalExecutorConfig | null;
@@ -1681,11 +1683,11 @@ export const createRuntimeSourceAuthService = (input: { · 10 unmodified lines
16811683 });
16821684 const mirrorLocalSourceResult = (
16831685 result: ExecutorSourceAddResult,
1684− ): Effect.Effect<ExecutorSourceAddResult, Error, never> =>
1686+ ): Effect.Effect<ExecutorSourceAddResult, Error, WorkspaceStorageServices> =>
16851687 Effect.succeed(result);
16861688 const mirrorLocalMcpSourceResult = (
16871689 result: McpSourceConnectResult,
1688− ): Effect.Effect<McpSourceConnectResult, Error, never> =>
1690+ ): Effect.Effect<McpSourceConnectResult, Error, WorkspaceStorageServices> =>
16891691 Effect.succeed(result);
16901692 const provideLocalWorkspace = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
16911693 input.localWorkspaceState
@@ -2250,7 +2252,7 @@ export const RuntimeSourceAuthServiceLive = (input: { · 558 unmodified lines
22502252 Layer.effect(
22512253 RuntimeSourceAuthServiceTag,
22522254 Effect.gen(function* () {
2253− const rows = yield* SqlControlPlaneRowsService;
2255+ const rows = yield* ControlPlaneStore;
22542256 const liveExecutionManager = yield* LiveExecutionManagerService;
22552257
22562258 return createRuntimeSourceAuthService({
packages/control-plane/src/runtime/source-definitions.test.ts+11−11
@@ -129,7 +129,7 @@ describe("source-definitions", () => { · 128 unmodified lines
129129 headerName: "Authorization",
130130 prefix: "Bearer ",
131131 token: {
132− providerId: "postgres",
132+ providerId: "local",
133133 handle: "sec_token",
134134 },
135135 },
@@ -166,7 +166,7 @@ describe("source-definitions", () => { · 30 unmodified lines
166166 headerName: " ",
167167 prefix: undefined,
168168 accessToken: {
169− providerId: " postgres ",
169+ providerId: " local ",
170170 handle: " sec_access ",
171171 },
172172 refreshToken: null,
@@ -180,7 +180,7 @@ describe("source-definitions", () => { · 7 unmodified lines
180180 headerName: "Authorization",
181181 prefix: "Bearer ",
182182 accessToken: {
183− providerId: "postgres",
183+ providerId: "local",
184184 handle: "sec_access",
185185 },
186186 refreshToken: null,
@@ -222,11 +222,11 @@ describe("source-definitions", () => { · 35 unmodified lines
222222 headerName: "Authorization",
223223 prefix: "Bearer ",
224224 accessToken: {
225− providerId: "postgres",
225+ providerId: "local",
226226 handle: "sec_access",
227227 },
228228 refreshToken: {
229− providerId: "postgres",
229+ providerId: "local",
230230 handle: " ",
231231 },
232232 } as never,
@@ -319,7 +319,7 @@ describe("source-definitions", () => { · 86 unmodified lines
319319 headerName: " ",
320320 prefix: "Token ",
321321 token: {
322− providerId: " postgres ",
322+ providerId: " local ",
323323 handle: " sec_token ",
324324 },
325325 } as never,
@@ -332,7 +332,7 @@ describe("source-definitions", () => { · 6 unmodified lines
332332 headerName: "Authorization",
333333 prefix: "Token ",
334334 token: {
335− providerId: "postgres",
335+ providerId: "local",
336336 handle: "sec_token",
337337 },
338338 });
@@ -353,7 +353,7 @@ describe("source-definitions", () => { · 14 unmodified lines
353353 headerName: "Authorization",
354354 prefix: "Bearer ",
355355 token: {
356− providerId: "postgres",
356+ providerId: "local",
357357 handle: "sec_bearer",
358358 },
359359 },
@@ -397,11 +397,11 @@ describe("source-definitions", () => { · 37 unmodified lines
397397 headerName: "Authorization",
398398 prefix: "Bearer ",
399399 accessToken: {
400− providerId: "postgres",
400+ providerId: "local",
401401 handle: "sec_access",
402402 },
403403 refreshToken: {
404− providerId: "postgres",
404+ providerId: "local",
405405 handle: "sec_refresh",
406406 },
407407 },
@@ -416,7 +416,7 @@ describe("source-definitions", () => { · 8 unmodified lines
416416 headerName: "Authorization",
417417 prefix: "Bearer ",
418418 accessToken: {
419− providerId: "postgres",
419+ providerId: "local",
420420 handle: "sec_access",
421421 },
422422 refreshToken: null,
packages/control-plane/src/runtime/source-inspection.test.ts+0−344
@@ -1,344 +0,0 @@
1−import { mkdtempSync } from "node:fs";
2−import { tmpdir } from "node:os";
3−import { join } from "node:path";
4−
5−import { describe, expect, it } from "vitest";
6−import * as Effect from "effect/Effect";
7−
8−import {
9− AccountIdSchema,
10− type LocalConfigSource,
11− SourceIdSchema,
12− SourceRecipeRevisionIdSchema,
13− SourceRecipeSchemaBundleIdSchema,
14− WorkspaceIdSchema,
15− type Source,
16− type StoredSourceRecipeOperationRecord,
17−} from "#schema";
18−import {
19− createSqlControlPlanePersistence,
20− type SqlControlPlanePersistence,
21−} from "#persistence";
22−
23−import {
24− loadLocalExecutorConfig,
25− resolveLocalWorkspaceContext,
26− writeProjectLocalExecutorConfig,
27−} from "./local-config";
28−import {
29− buildLocalSourceArtifact,
30− writeLocalSourceArtifact,
31−} from "./local-source-artifacts";
32−import {
33− getSourceInspection,
34− getSourceInspectionSchemaBundle,
35− getSourceInspectionToolDetail,
36−} from "./source-inspection";
37−import {
38− RuntimeLocalWorkspaceService,
39− type RuntimeLocalWorkspaceState,
40−} from "./local-runtime-context";
41−import {
42− writeLocalWorkspaceState,
43−} from "./local-workspace-state";
44−import { ControlPlaneStore } from "./store";
45−
46−const makePersistence = () =>
47− Effect.runPromise(
48− createSqlControlPlanePersistence({
49− localDataDir: ":memory:",
50− }),
51− );
52−
53−const makeRuntimeLocalWorkspaceState = (
54− workspaceId: ReturnType<typeof WorkspaceIdSchema.make>,
55− accountId: ReturnType<typeof AccountIdSchema.make>,
56−) =>
57− Effect.runPromise(
58− Effect.gen(function* () {
59− const workspaceRoot = mkdtempSync(
60− join(tmpdir(), "executor-source-inspection-"),
61− );
62− const context = yield* resolveLocalWorkspaceContext({ workspaceRoot });
63− const loadedConfig = yield* loadLocalExecutorConfig(context);
64−
65− return {
66− context,
67− installation: {
68− workspaceId,
69− accountId,
70− },
71− loadedConfig,
72− } satisfies RuntimeLocalWorkspaceState;
73− }),
74− );
75−
76−const makeSource = (input: {
77− workspaceId: Source["workspaceId"];
78− sourceId: Source["id"];
79−}): Source => ({
80− id: input.sourceId,
81− workspaceId: input.workspaceId,
82− name: "Cloudflare API",
83− kind: "openapi",
84− endpoint: "https://api.cloudflare.com/client/v4",
85− status: "connected",
86− enabled: true,
87− namespace: "cloudflare.api",
88− bindingVersion: 1,
89− binding: {
90− specUrl: "https://example.com/openapi.json",
91− defaultHeaders: null,
92− },
93− importAuthPolicy: "reuse_runtime",
94− importAuth: { kind: "none" },
95− auth: { kind: "none" },
96− sourceHash: null,
97− lastError: null,
98− createdAt: 1000,
99− updatedAt: 1000,
100−});
101−
102−const makeOperation = (
103− overrides: Partial<StoredSourceRecipeOperationRecord> = {},
104−): StoredSourceRecipeOperationRecord => ({
105− id: "src_recipe_op_inspection",
106− recipeRevisionId: SourceRecipeRevisionIdSchema.make("src_recipe_rev_inspection"),
107− operationKey: "zones.listZones",
108− transportKind: "http",
109− toolId: "zones.listZones",
110− title: "List zones",
111− description: "List Cloudflare zones",
112− operationKind: "read",
113− searchText: "zones list cloudflare",
114− inputSchemaJson: JSON.stringify({
115− type: "object",
116− properties: {
117− page: { type: "number" },
118− },
119− }),
120− outputSchemaJson: JSON.stringify({
121− type: "object",
122− properties: {
123− result: {
124− type: "array",
125− items: { type: "string" },
126− },
127− },
128− }),
129− providerKind: "openapi",
130− providerDataJson: JSON.stringify({
131− kind: "openapi",
132− toolId: "zones.listZones",
133− rawToolId: "zones_listZones",
134− operationId: "zones.listZones",
135− group: "zones",
136− leaf: "listZones",
137− tags: ["zones"],
138− method: "get",
139− path: "/zones",
140− operationHash: "hash",
141− invocation: {
142− method: "get",
143− pathTemplate: "/zones",
144− parameters: [],
145− requestBody: null,
146− },
147− }),
148− createdAt: 1000,
149− updatedAt: 1000,
150− ...overrides,
151−});
152−
153−const configSourceFromSource = (source: Source): LocalConfigSource => ({
154− kind: "openapi",
155− ...(source.name !== source.id ? { name: source.name } : {}),
156− ...(source.namespace && source.namespace !== source.id
157− ? { namespace: source.namespace }
158− : {}),
159− connection: {
160− endpoint: source.endpoint,
161− },
162− binding: {
163− specUrl:
164− typeof source.binding.specUrl === "string"
165− ? source.binding.specUrl
166− : "https://example.com/openapi.json",
167− defaultHeaders:
168− (source.binding.defaultHeaders as Record<string, string> | null | undefined)
169− ?? null,
170− },
171−});
172−
173−describe("source-inspection", () => {
174− it("returns a lightweight inspection bundle and loads rich detail on demand", async () => {
175− const persistence = await makePersistence();
176− try {
177− const workspaceId = WorkspaceIdSchema.make("ws_source_inspection");
178− const accountId = AccountIdSchema.make("acc_source_inspection");
179− const sourceId = SourceIdSchema.make("src_source_inspection");
180− const recipeRevisionId =
181− SourceRecipeRevisionIdSchema.make("src_recipe_rev_materialization");
182− const runtimeLocalWorkspace = await makeRuntimeLocalWorkspaceState(
183− workspaceId,
184− accountId,
185− );
186− const hugeDocument = JSON.stringify({
187− openapi: "3.0.3",
188− info: {
189− title: "Cloudflare API",
190− version: "1.0.0",
191− },
192− paths: Object.fromEntries(
193− Array.from({ length: 256 }, (_, index) => [
194− `/zones/${index}`,
195− {
196− get: {
197− operationId: `zones.listZones${index}`,
198− responses: { 200: { description: "ok" } },
199− },
200− },
201− ]),
202− ),
203− });
204−
205− const source = makeSource({
206− workspaceId,
207− sourceId,
208− });
209− await Effect.runPromise(writeProjectLocalExecutorConfig({
210− context: runtimeLocalWorkspace.context,
211− config: {
212− sources: {
213− [sourceId]: configSourceFromSource(source),
214− },
215− },
216− }));
217− await Effect.runPromise(writeLocalSourceArtifact({
218− context: runtimeLocalWorkspace.context,
219− sourceId,
220− artifact: buildLocalSourceArtifact({
221− source: {
222− ...source,
223− sourceHash: "manifest_hash",
224− },
225− materialization: {
226− manifestJson: JSON.stringify({
227− sourceHash: "manifest_hash",
228− }),
229− manifestHash: "manifest_hash",
230− sourceHash: "manifest_hash",
231− documents: [{
232− id: "src_recipe_doc_inspection",
233− recipeRevisionId,
234− documentKind: "openapi",
235− documentKey: "https://example.com/openapi.json",
236− contentText: hugeDocument,
237− contentHash: "doc_hash",
238− fetchedAt: 1000,
239− createdAt: 1000,
240− updatedAt: 1000,
241− }],
242− schemaBundles: [{
243− id: SourceRecipeSchemaBundleIdSchema.make(
244− "src_recipe_bundle_inspection",
245− ),
246− recipeRevisionId,
247− bundleKind: "json_schema_ref_map",
248− refsJson: JSON.stringify({
249− "#/components/schemas/Pagination": {
250− type: "object",
251− properties: {
252− page: { type: "number" },
253− },
254− },
255− }),
256− contentHash: "bundle_hash",
257− createdAt: 1000,
258− updatedAt: 1000,
259− }],
260− operations: [makeOperation({
261− recipeRevisionId,
262− })],
263− },
264− }),
265− }));
266− await Effect.runPromise(writeLocalWorkspaceState({
267− context: runtimeLocalWorkspace.context,
268− state: {
269− version: 1,
270− sources: {
271− [sourceId]: {
272− status: "connected",
273− lastError: null,
274− sourceHash: "manifest_hash",
275− createdAt: 1000,
276− updatedAt: 1000,
277− },
278− },
279− policies: {},
280− },
281− }));
282−
283− const inspection = await Effect.runPromise(
284− getSourceInspection({
285− workspaceId,
286− sourceId,
287− }).pipe(
288− Effect.provideService(ControlPlaneStore, persistence.rows),
289− Effect.provideService(
290− RuntimeLocalWorkspaceService,
291− runtimeLocalWorkspace,
292− ),
293− ),
294− );
295−
296− expect(inspection.toolCount).toBe(1);
297− expect(inspection.tools[0]?.path).toBe("cloudflare.api.zones.listZones");
298− expect("manifestJson" in inspection).toBe(false);
299− expect("rawDocumentText" in inspection).toBe(false);
300− expect("definitionsJson" in inspection).toBe(false);
301−
302− const detail = await Effect.runPromise(
303− getSourceInspectionToolDetail({
304− workspaceId,
305− sourceId,
306− toolPath: "cloudflare.api.zones.listZones",
307− }).pipe(
308− Effect.provideService(ControlPlaneStore, persistence.rows),
309− Effect.provideService(
310− RuntimeLocalWorkspaceService,
311− runtimeLocalWorkspace,
312− ),
313− ),
314− );
315−
316− expect(detail.summary.method).toBe("get");
317− expect(detail.summary.inputType).toContain("page");
318− expect(detail.summary.outputType).toContain("result");
319− expect(detail.providerDataJson).toContain("/zones");
320− expect(detail.inputSchemaJson).toContain("\"page\"");
321− expect(detail.outputSchemaJson).toContain("\"result\"");
322− expect(detail.schemaBundleId).toBe("src_recipe_bundle_inspection");
323−
324− const schemaBundle = await Effect.runPromise(
325− getSourceInspectionSchemaBundle({
326− workspaceId,
327− sourceId,
328− schemaBundleId: "src_recipe_bundle_inspection",
329− }).pipe(
330− Effect.provideService(ControlPlaneStore, persistence.rows),
331− Effect.provideService(
332− RuntimeLocalWorkspaceService,
333− runtimeLocalWorkspace,
334− ),
335− ),
336− );
337−
338− expect(schemaBundle.kind).toBe("json_schema_ref_map");
339− expect(schemaBundle.refsJson).toContain("Pagination");
340− } finally {
341− await persistence.close();
342− }
343− });
344−});
packages/control-plane/src/runtime/source-materialization.ts+26−23
@@ -1,4 +1,3 @@
1−import type { SqlControlPlaneRows } from "#persistence";
21 import type {
32 AccountId,
43 Source,
@@ -6,16 +5,16 @@ import type { · 1 unmodified line
65 } from "#schema";
76 import * as Effect from "effect/Effect";
87
9−import {
10− buildLocalSourceArtifact,
11− writeLocalSourceArtifact,
12−} from "./local-source-artifacts";
8+import type { ControlPlaneStoreShape } from "./store";
139 import {
1410 requireRuntimeLocalWorkspace,
1511 } from "./local-runtime-context";
1612 import {
17− loadLocalWorkspaceState,
18− writeLocalWorkspaceState,
13+ type WorkspaceStorageServices,
14+ SourceArtifactStore,
15+ WorkspaceStateStore,
16+} from "./local-storage";
17+import {
1918 type LocalWorkspaceState,
2019 } from "./local-workspace-state";
2120 import { resolveSourceAuthMaterial } from "./source-auth-material";
@@ -24,7 +23,6 @@ import { · 2 unmodified lines
2423 } from "./source-adapters";
2524 import {
2625 materializationFromMcpManifestEntries,
27− persistMcpRecipeRevisionFromManifestEntries,
2826 } from "./source-adapters/mcp";
2927 import type {
3028 ResolveSecretMaterial as ResolveSourceSecretMaterial,
@@ -36,16 +34,20 @@ const shouldIndexSource = (source: Source): boolean => · 5 unmodified lines
3634 && getSourceAdapterForSource(source).family !== "internal";
3735
3836 export const syncSourceMaterialization = (input: {
39− rows: SqlControlPlaneRows;
37+ rows: ControlPlaneStoreShape;
4038 source: Source;
4139 actorAccountId?: AccountId | null;
4240 resolveSecretMaterial: ResolveSourceSecretMaterial;
43−}): Effect.Effect<void, Error, never> =>
41+}): Effect.Effect<void, Error, WorkspaceStorageServices> =>
4442 Effect.gen(function* () {
4543 const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(input.source.workspaceId);
44+ const workspaceStateStore = yield* WorkspaceStateStore;
45+ const sourceArtifactStore = yield* SourceArtifactStore;
4646
4747 if (!shouldIndexSource(input.source)) {
48− const state = yield* loadLocalWorkspaceState(runtimeLocalWorkspace.context);
48+ const state = yield* workspaceStateStore.load(
49+ runtimeLocalWorkspace.context,
50+ );
4951 const existingSourceState = state.sources[input.source.id];
5052 const nextState: LocalWorkspaceState = {
5153 ...state,
@@ -60,7 +62,7 @@ export const syncSourceMaterialization = (input: { · 8 unmodified lines
6062 },
6163 },
6264 };
63− yield* writeLocalWorkspaceState({
65+ yield* workspaceStateStore.write({
6466 context: runtimeLocalWorkspace.context,
6567 state: nextState,
6668 });
@@ -80,16 +82,18 @@ export const syncSourceMaterialization = (input: { · 13 unmodified lines
8082 resolveSecretMaterial: input.resolveSecretMaterial,
8183 }),
8284 });
83− yield* writeLocalSourceArtifact({
85+ yield* sourceArtifactStore.write({
8486 context: runtimeLocalWorkspace.context,
8587 sourceId: input.source.id,
86− artifact: buildLocalSourceArtifact({
88+ artifact: sourceArtifactStore.build({
8789 source: input.source,
8890 materialization,
8991 }),
9092 });
9193
92− const state = yield* loadLocalWorkspaceState(runtimeLocalWorkspace.context);
94+ const state = yield* workspaceStateStore.load(
95+ runtimeLocalWorkspace.context,
96+ );
9397 const existingSourceState = state.sources[input.source.id];
9498 const nextState: LocalWorkspaceState = {
9599 ...state,
@@ -104,31 +108,30 @@ export const syncSourceMaterialization = (input: { · 8 unmodified lines
104108 },
105109 },
106110 };
107− yield* writeLocalWorkspaceState({
111+ yield* workspaceStateStore.write({
108112 context: runtimeLocalWorkspace.context,
109113 state: nextState,
110114 });
111115 });
112116
113117 export const persistMcpRecipeMaterializationFromManifest = (input: {
114− rows: SqlControlPlaneRows;
118+ rows: ControlPlaneStoreShape;
115119 source: Source;
116− manifestEntries: Parameters<
117− typeof persistMcpRecipeRevisionFromManifestEntries
118− >[0]["manifestEntries"];
119−}): Effect.Effect<void, Error, never> =>
120+ manifestEntries: Parameters<typeof materializationFromMcpManifestEntries>[0]["manifestEntries"];
121+}): Effect.Effect<void, Error, WorkspaceStorageServices> =>
120122 Effect.gen(function* () {
121123 const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(input.source.workspaceId);
124+ const sourceArtifactStore = yield* SourceArtifactStore;
122125 const materialization = materializationFromMcpManifestEntries({
123126 recipeRevisionId: "src_recipe_rev_materialization" as never,
124127 endpoint: input.source.endpoint,
125128 manifestEntries: input.manifestEntries,
126129 });
127130
128− yield* writeLocalSourceArtifact({
131+ yield* sourceArtifactStore.write({
129132 context: runtimeLocalWorkspace.context,
130133 sourceId: input.source.id,
131− artifact: buildLocalSourceArtifact({
134+ artifact: sourceArtifactStore.build({
132135 source: input.source,
133136 materialization,
134137 }),
packages/control-plane/src/runtime/source-recipe-support.ts+1−348
@@ -1,22 +1,10 @@
11 import { sha256Hex } from "@executor/codemode-core";
22
33 import type {
4− Source,
54 StoredSourceRecipeDocumentRecord,
65 StoredSourceRecipeOperationRecord,
76 StoredSourceRecipeSchemaBundleRecord,
8− StoredSourceRecipeRevisionRecord,
97 } from "#schema";
10−import type { SqlControlPlaneRows } from "#persistence";
11−import * as Effect from "effect/Effect";
12−import * as Option from "effect/Option";
13−
14−type SourceBindingRecord =
15− Awaited<
16− ReturnType<SqlControlPlaneRows["sources"]["getByWorkspaceAndId"]>
17− > extends Effect.Effect<Option.Option<infer T>, unknown, never>
18− ? T
19− : never;
208
219 export const normalizeSearchText = (
2210 ...parts: ReadonlyArray<string | null | undefined>
@@ -27,32 +15,7 @@ export const normalizeSearchText = ( · 4 unmodified lines
2715 .join(" ")
2816 .toLowerCase();
2917
30−export const contentHash = (value: string): string =>
31− sha256Hex(value);
32−
33−export const loadSourceBindingRecord = (input: {
34− rows: SqlControlPlaneRows;
35− source: Source;
36−}): Effect.Effect<SourceBindingRecord, Error, never> =>
37− Effect.gen(function* () {
38− const sourceRecord = yield* input.rows.sources
39− .getByWorkspaceAndId(input.source.workspaceId, input.source.id)
40− .pipe(
41− Effect.mapError((cause) =>
42− cause instanceof Error ? cause : new Error(String(cause)),
43− ),
44− );
45−
46− if (Option.isNone(sourceRecord)) {
47− return yield* Effect.fail(
48− new Error(
49− `Source disappeared while syncing recipe data for ${input.source.id}`,
50− ),
51− );
52− }
53−
54− return sourceRecord.value;
55− });
18+export const contentHash = (value: string): string => sha256Hex(value);
5619
5720 export type SourceRecipeMaterialization = {
5821 manifestJson: string | null;
@@ -62,313 +25,3 @@ export type SourceRecipeMaterialization = { · 3 unmodified lines
6225 schemaBundles: readonly StoredSourceRecipeSchemaBundleRecord[];
6326 operations: readonly StoredSourceRecipeOperationRecord[];
6427 };
65−
66−const canonicalMaterializationHash = (input: SourceRecipeMaterialization): string => {
67− const documents = [...input.documents]
68− .map((document) => ({
69− documentKind: document.documentKind,
70− documentKey: document.documentKey,
71− contentHash: document.contentHash,
72− }))
73− .sort((left, right) =>
74− left.documentKind.localeCompare(right.documentKind)
75− || left.documentKey.localeCompare(right.documentKey)
76− || left.contentHash.localeCompare(right.contentHash)
77− );
78− const schemaBundles = [...input.schemaBundles]
79− .map((bundle) => ({
80− bundleKind: bundle.bundleKind,
81− contentHash: bundle.contentHash,
82− }))
83− .sort((left, right) =>
84− left.bundleKind.localeCompare(right.bundleKind)
85− || left.contentHash.localeCompare(right.contentHash)
86− );
87− const operations = [...input.operations]
88− .map((operation) => ({
89− operationKey: operation.operationKey,
90− transportKind: operation.transportKind,
91− toolId: operation.toolId,
92− title: operation.title,
93− description: operation.description,
94− operationKind: operation.operationKind,
95− searchText: operation.searchText,
96− inputSchemaJson: operation.inputSchemaJson,
97− outputSchemaJson: operation.outputSchemaJson,
98− providerKind: operation.providerKind,
99− providerDataJson: operation.providerDataJson,
100− }))
101− .sort((left, right) => left.operationKey.localeCompare(right.operationKey));
102−
103− return contentHash(JSON.stringify({
104− schemaVersion: 1,
105− manifestHash: input.manifestHash,
106− manifestJson: input.manifestJson,
107− documents,
108− schemaBundles,
109− operations,
110− }));
111−};
112−
113−const replaceRevisionContent = (input: {
114− rows: SqlControlPlaneRows;
115− recipeRevisionId: StoredSourceRecipeRevisionRecord["id"];
116− materializationHash: string;
117− materialization: SourceRecipeMaterialization;
118− updatedAt: number;
119−}) =>
120− Effect.gen(function* () {
121− const boundDocuments = input.materialization.documents.map((document) => ({
122− ...document,
123− recipeRevisionId: input.recipeRevisionId,
124− }));
125− const boundSchemaBundles = input.materialization.schemaBundles.map((bundle) => ({
126− ...bundle,
127− recipeRevisionId: input.recipeRevisionId,
128− }));
129− const boundOperations = input.materialization.operations.map((operation) => ({
130− ...operation,
131− recipeRevisionId: input.recipeRevisionId,
132− }));
133−
134− yield* input.rows.sourceRecipeRevisions
135− .update(input.recipeRevisionId, {
136− manifestJson: input.materialization.manifestJson,
137− manifestHash: input.materialization.manifestHash,
138− materializationHash: input.materializationHash,
139− updatedAt: input.updatedAt,
140− })
141− .pipe(
142− Effect.mapError((cause) =>
143− cause instanceof Error ? cause : new Error(String(cause)),
144− ),
145− );
146−
147− yield* input.rows.sourceRecipeDocuments
148− .replaceForRevision({
149− recipeRevisionId: input.recipeRevisionId,
150− documents: boundDocuments,
151− })
152− .pipe(
153− Effect.mapError((cause) =>
154− cause instanceof Error ? cause : new Error(String(cause)),
155− ),
156− );
157−
158− yield* input.rows.sourceRecipeSchemaBundles
159− .replaceForRevision({
160− recipeRevisionId: input.recipeRevisionId,
161− bundles: boundSchemaBundles,
162− })
163− .pipe(
164− Effect.mapError((cause) =>
165− cause instanceof Error ? cause : new Error(String(cause)),
166− ),
167− );
168−
169− yield* input.rows.sourceRecipeOperations
170− .replaceForRevision({
171− recipeRevisionId: input.recipeRevisionId,
172− operations: boundOperations,
173− })
174− .pipe(
175− Effect.mapError((cause) =>
176− cause instanceof Error ? cause : new Error(String(cause)),
177− ),
178− );
179− });
180−
181−const cleanupOrphanedRevision = (input: {
182− rows: SqlControlPlaneRows;
183− recipeRevisionId: StoredSourceRecipeRevisionRecord["id"];
184−}) =>
185− Effect.gen(function* () {
186− const referenceCount = yield* input.rows.sources.countByRecipeRevisionId(input.recipeRevisionId);
187− if (referenceCount > 0) {
188− return;
189− }
190−
191− yield* input.rows.sourceRecipeDocuments.removeByRevisionId(input.recipeRevisionId);
192− yield* input.rows.sourceRecipeSchemaBundles.removeByRevisionId(input.recipeRevisionId);
193− yield* input.rows.sourceRecipeOperations.removeByRevisionId(input.recipeRevisionId);
194− yield* input.rows.sourceRecipeRevisions.removeById(input.recipeRevisionId);
195− });
196−
197−const updateRecipeLatestRevision = (input: {
198− rows: SqlControlPlaneRows;
199− recipeId: SourceBindingRecord["recipeId"];
200− latestRevisionId: StoredSourceRecipeRevisionRecord["id"];
201− updatedAt: number;
202−}) =>
203− Effect.gen(function* () {
204− const recipe = yield* input.rows.sourceRecipes.getById(input.recipeId);
205− if (Option.isNone(recipe)) {
206− return yield* Effect.fail(
207− new Error(`Recipe disappeared while syncing recipe data for ${input.recipeId}`),
208− );
209− }
210−
211− yield* input.rows.sourceRecipes.upsert({
212− ...recipe.value,
213− latestRevisionId: input.latestRevisionId,
214− updatedAt: input.updatedAt,
215− });
216− });
217−
218−export const persistRecipeMaterialization = (input: {
219− rows: SqlControlPlaneRows;
220− source: Source;
221− materialization: SourceRecipeMaterialization;
222−}): Effect.Effect<void, Error, never> =>
223− Effect.gen(function* () {
224− const sourceRecord = yield* loadSourceBindingRecord({
225− rows: input.rows,
226− source: input.source,
227− });
228− const now = Date.now();
229− const materializationHash = canonicalMaterializationHash(input.materialization);
230− const currentRevision = yield* input.rows.sourceRecipeRevisions.getById(
231− sourceRecord.recipeRevisionId,
232− );
233−
234− if (Option.isNone(currentRevision)) {
235− return yield* Effect.fail(
236− new Error(
237− `Recipe revision disappeared while syncing recipe data for ${input.source.id}`,
238− ),
239− );
240− }
241−
242− const existingMaterializedRevision = yield* input.rows.sourceRecipeRevisions
243− .getByRecipeAndMaterializationHash({
244− recipeId: sourceRecord.recipeId,
245− materializationHash,
246− });
247−
248− if (
249− Option.isSome(existingMaterializedRevision)
250− && existingMaterializedRevision.value.id !== currentRevision.value.id
251− ) {
252− const movedSource = yield* input.rows.sources.update(
253− input.source.workspaceId,
254− input.source.id,
255− {
256− recipeRevisionId: existingMaterializedRevision.value.id,
257− sourceHash: input.materialization.sourceHash,
258− updatedAt: now,
259− },
260− );
261− if (Option.isNone(movedSource)) {
262− return yield* Effect.fail(
263− new Error(`Source disappeared while updating recipe materialization for ${input.source.id}`),
264− );
265− }
266−
267− yield* updateRecipeLatestRevision({
268− rows: input.rows,
269− recipeId: sourceRecord.recipeId,
270− latestRevisionId: existingMaterializedRevision.value.id,
271− updatedAt: now,
272− });
273− yield* cleanupOrphanedRevision({
274− rows: input.rows,
275− recipeRevisionId: currentRevision.value.id,
276− });
277− return;
278− }
279−
280− const needsFreshRevision =
281− currentRevision.value.materializationHash !== null
282− && currentRevision.value.materializationHash !== materializationHash;
283−
284− const nextRevisionNumber = needsFreshRevision
285− ? yield* input.rows.sourceRecipeRevisions.nextRevisionNumber(sourceRecord.recipeId)
286− : null;
287−
288− const targetRevision = needsFreshRevision
289− ? createFreshTargetRevision({
290− currentRevision: currentRevision.value,
291− materializationHash,
292− now,
293− nextRevisionNumber: nextRevisionNumber!,
294− })
295− : {
296− revision: currentRevision.value,
297− movedFromRevisionId: null as StoredSourceRecipeRevisionRecord["id"] | null,
298− };
299−
300− if (needsFreshRevision) {
301− yield* input.rows.sourceRecipeRevisions.upsert(targetRevision.revision);
302− const movedSource = yield* input.rows.sources.update(
303− input.source.workspaceId,
304− input.source.id,
305− {
306− recipeRevisionId: targetRevision.revision.id,
307− sourceHash: input.materialization.sourceHash,
308− updatedAt: now,
309− },
310− );
311− if (Option.isNone(movedSource)) {
312− return yield* Effect.fail(
313− new Error(`Source disappeared while switching recipe materialization for ${input.source.id}`),
314− );
315− }
316− } else {
317− const updatedSource = yield* input.rows.sources.update(
318− input.source.workspaceId,
319− input.source.id,
320− {
321− sourceHash: input.materialization.sourceHash,
322− updatedAt: now,
323− },
324− );
325− if (Option.isNone(updatedSource)) {
326− return yield* Effect.fail(
327− new Error(`Source disappeared while updating sync metadata for ${input.source.id}`),
328− );
329− }
330− }
331−
332− yield* replaceRevisionContent({
333− rows: input.rows,
334− recipeRevisionId: targetRevision.revision.id,
335− materializationHash,
336− materialization: input.materialization,
337− updatedAt: now,
338− });
339− yield* updateRecipeLatestRevision({
340− rows: input.rows,
341− recipeId: sourceRecord.recipeId,
342− latestRevisionId: targetRevision.revision.id,
343− updatedAt: now,
344− });
345−
346− if (targetRevision.movedFromRevisionId !== null) {
347− yield* cleanupOrphanedRevision({
348− rows: input.rows,
349− recipeRevisionId: targetRevision.movedFromRevisionId,
350− });
351− }
352− });
353−
354−const createFreshTargetRevision = (input: {
355− currentRevision: StoredSourceRecipeRevisionRecord;
356− materializationHash: string;
357− now: number;
358− nextRevisionNumber: number;
359−}): {
360− revision: StoredSourceRecipeRevisionRecord;
361− movedFromRevisionId: StoredSourceRecipeRevisionRecord["id"] | null;
362−} => ({
363− revision: {
364− ...input.currentRevision,
365− id: `src_recipe_rev_${crypto.randomUUID()}` as StoredSourceRecipeRevisionRecord["id"],
366− revisionNumber: input.nextRevisionNumber,
367− manifestJson: null,
368− manifestHash: null,
369− materializationHash: input.materializationHash,
370− createdAt: input.now,
371− updatedAt: input.now,
372− },
373− movedFromRevisionId: input.currentRevision.id,
374−});
packages/control-plane/src/runtime/source-recipes-runtime.test.ts+0−858
@@ -1,858 +0,0 @@
1−import { mkdtempSync } from "node:fs";
2−import { tmpdir } from "node:os";
3−import { join } from "node:path";
4−
5−import { describe, expect, it } from "vitest";
6−import * as Effect from "effect/Effect";
7−import {
8− buildOpenApiToolPresentation,
9− compileOpenApiToolDefinitions,
10− extractOpenApiManifest,
11−} from "@executor/codemode-openapi";
12−
13−import {
14− AccountIdSchema,
15− SourceIdSchema,
16− type LocalConfigSource,
17− SourceRecipeIdSchema,
18− SourceRecipeRevisionIdSchema,
19− WorkspaceIdSchema,
20− type Source,
21− type StoredSourceRecipeOperationRecord,
22−} from "#schema";
23−import {
24− createSqlControlPlanePersistence,
25− type SqlControlPlanePersistence,
26−} from "#persistence";
27−
28−import {
29− loadLocalExecutorConfig,
30− resolveLocalWorkspaceContext,
31− writeProjectLocalExecutorConfig,
32−} from "./local-config";
33−import {
34− buildLocalSourceArtifact,
35− writeLocalSourceArtifact,
36−} from "./local-source-artifacts";
37−import {
38− RuntimeLocalWorkspaceService,
39− type RuntimeLocalWorkspaceState,
40−} from "./local-runtime-context";
41−import {
42− writeLocalWorkspaceState,
43−} from "./local-workspace-state";
44−import {
45− expandRecipeTools,
46− loadSourceWithRecipe,
47− loadWorkspaceSourceRecipes,
48− recipeToolDescriptor,
49− recipeToolPath,
50− recipeToolSearchNamespace,
51− type LoadedSourceRecipe,
52−} from "./source-recipes-runtime";
53−
54−const makePersistence = () =>
55− Effect.runPromise(
56− createSqlControlPlanePersistence({
57− localDataDir: ":memory:",
58− }),
59− );
60−
61−const makeRuntimeLocalWorkspaceState = (
62− workspaceId: ReturnType<typeof WorkspaceIdSchema.make>,
63−) =>
64− Effect.runPromise(
65− Effect.gen(function* () {
66− const workspaceRoot = mkdtempSync(
67− join(tmpdir(), "executor-source-recipes-runtime-"),
68− );
69− const context = yield* resolveLocalWorkspaceContext({ workspaceRoot });
70− const loadedConfig = yield* loadLocalExecutorConfig(context);
71−
72− return {
73− context,
74− installation: {
75− workspaceId,
76− accountId: AccountIdSchema.make("acc_local_source_recipes"),
77− },
78− loadedConfig,
79− } satisfies RuntimeLocalWorkspaceState;
80− }),
81− );
82−
83−const withRuntimeLocalWorkspace = <A, E>(
84− effect: Effect.Effect<A, E, never>,
85− runtimeLocalWorkspace: RuntimeLocalWorkspaceState,
86−) =>
87− effect.pipe(
88− Effect.provideService(RuntimeLocalWorkspaceService, runtimeLocalWorkspace),
89− );
90−
91−const openApiBindingConfigJson = (specUrl: string): string =>
92− JSON.stringify({
93− adapterKey: "openapi",
94− version: 1,
95− payload: {
96− specUrl,
97− defaultHeaders: null,
98− },
99− });
100−
101−const graphqlBindingConfigJson = (): string =>
102− JSON.stringify({
103− adapterKey: "graphql",
104− version: 1,
105− payload: {
106− defaultHeaders: null,
107− },
108− });
109−
110−const makeSource = (overrides: Partial<Source> = {}): Source => {
111− const kind = overrides.kind ?? "openapi";
112− const endpoint = overrides.endpoint
113− ?? (kind === "graphql"
114− ? "https://example.com/graphql"
115− : kind === "mcp"
116− ? "https://example.com/mcp"
117− : "https://api.github.com");
118− const binding =
119− overrides.binding
120− ?? (kind === "openapi"
121− ? {
122− specUrl: "https://api.github.com/openapi.json",
123− defaultHeaders: null,
124− }
125− : kind === "graphql"
126− ? {
127− defaultHeaders: null,
128− }
129− : kind === "mcp"
130− ? {
131− transport: null,
132− queryParams: null,
133− headers: null,
134− }
135− : {});
136−
137− return {
138− id: SourceIdSchema.make("src_runtime_recipe"),
139− workspaceId: WorkspaceIdSchema.make("ws_runtime_recipe"),
140− name: "GitHub",
141− kind,
142− endpoint,
143− status: "connected",
144− enabled: true,
145− namespace: "github",
146− bindingVersion: 1,
147− binding,
148− importAuthPolicy: "reuse_runtime",
149− importAuth: { kind: "none" },
150− auth: { kind: "none" },
151− sourceHash: null,
152− lastError: null,
153− createdAt: 1000,
154− updatedAt: 1000,
155− ...overrides,
156− };
157−};
158−
159−const makeOperation = (
160− overrides: Partial<StoredSourceRecipeOperationRecord> = {},
161−): StoredSourceRecipeOperationRecord => ({
162− id: "src_recipe_op_runtime",
163− recipeRevisionId: SourceRecipeRevisionIdSchema.make("src_recipe_rev_runtime"),
164− operationKey: "getRepo",
165− transportKind: "http",
166− toolId: "getRepo",
167− title: "Get Repo",
168− description: "Read a repository",
169− operationKind: "read",
170− searchText: "get repo github",
171− inputSchemaJson: JSON.stringify({
172− type: "object",
173− additionalProperties: false,
174− }),
175− outputSchemaJson: JSON.stringify({
176− type: "object",
177− properties: {
178− full_name: { type: "string" },
179− },
180− }),
181− providerKind: "openapi",
182− providerDataJson: JSON.stringify({
183− kind: "openapi",
184− toolId: "getRepo",
185− rawToolId: "repos_getRepo",
186− operationId: "repos.getRepo",
187− group: "repos",
188− leaf: "getRepo",
189− tags: ["repos"],
190− method: "get",
191− path: "/repos/{owner}/{repo}",
192− operationHash: "hash",
193− invocation: {
194− method: "get",
195− pathTemplate: "/repos/{owner}/{repo}",
196− parameters: [],
197− requestBody: null,
198− },
199− }),
200− createdAt: 1000,
201− updatedAt: 1000,
202− ...overrides,
203−});
204−
205−const makeGraphqlOperation = (
206− overrides: Partial<StoredSourceRecipeOperationRecord> = {},
207−): StoredSourceRecipeOperationRecord => ({
208− id: "src_recipe_op_graphql_runtime",
209− recipeRevisionId: SourceRecipeRevisionIdSchema.make("src_recipe_rev_runtime"),
210− operationKey: "viewer",
211− transportKind: "graphql",
212− toolId: "viewer",
213− title: "Viewer",
214− description: "Query the current viewer",
215− operationKind: "read",
216− searchText: "viewer graphql query",
217− inputSchemaJson: JSON.stringify({
218− type: "object",
219− additionalProperties: false,
220− }),
221− outputSchemaJson: JSON.stringify({
222− type: "object",
223− properties: {
224− login: { type: "string" },
225− },
226− }),
227− providerKind: "graphql",
228− providerDataJson: JSON.stringify({
229− kind: "graphql",
230− toolKind: "field",
231− toolId: "viewer",
232− rawToolId: "viewer",
233− group: "query",
234− leaf: "viewer",
235− fieldName: "viewer",
236− operationType: "query",
237− operationName: "viewer",
238− operationDocument: "query Viewer { viewer { login } }",
239− queryTypeName: "Query",
240− mutationTypeName: null,
241− subscriptionTypeName: null,
242− }),
243− createdAt: 1000,
244− updatedAt: 1000,
245− ...overrides,
246−});
247−
248−const makeMcpOperation = (
249− overrides: Partial<StoredSourceRecipeOperationRecord> = {},
250−): StoredSourceRecipeOperationRecord => ({
251− id: "src_recipe_op_mcp_runtime",
252− recipeRevisionId: SourceRecipeRevisionIdSchema.make("src_recipe_rev_runtime"),
253− operationKey: "echo",
254− transportKind: "mcp",
255− toolId: "echo",
256− title: "Echo",
257− description: "Echo a value",
258− operationKind: "unknown",
259− searchText: "echo mcp",
260− inputSchemaJson: null,
261− outputSchemaJson: null,
262− providerKind: "mcp",
263− providerDataJson: JSON.stringify({
264− kind: "mcp",
265− toolId: "echo",
266− toolName: "echo",
267− description: "Echo a value",
268− }),
269− createdAt: 1000,
270− updatedAt: 1000,
271− ...overrides,
272−});
273−
274−const configSourceFromSource = (source: Source): LocalConfigSource => {
275− switch (source.kind) {
276− case "openapi":
277− return {
278− kind: "openapi" as const,
279− ...(source.name !== source.id ? { name: source.name } : {}),
280− ...(source.namespace && source.namespace !== source.id
281− ? { namespace: source.namespace }
282− : {}),
283− ...(source.enabled ? {} : { enabled: false }),
284− connection: {
285− endpoint: source.endpoint,
286− },
287− binding: {
288− specUrl:
289− typeof source.binding.specUrl === "string"
290− ? source.binding.specUrl
291− : "https://example.com/openapi.json",
292− defaultHeaders:
293− (source.binding.defaultHeaders as Record<string, string> | null | undefined)
294− ?? null,
295− },
296− };
297− case "graphql":
298− return {
299− kind: "graphql" as const,
300− ...(source.name !== source.id ? { name: source.name } : {}),
301− ...(source.namespace && source.namespace !== source.id
302− ? { namespace: source.namespace }
303− : {}),
304− ...(source.enabled ? {} : { enabled: false }),
305− connection: {
306− endpoint: source.endpoint,
307− },
308− binding: {
309− defaultHeaders:
310− (source.binding.defaultHeaders as Record<string, string> | null | undefined)
311− ?? null,
312− },
313− };
314− case "mcp":
315− return {
316− kind: "mcp" as const,
317− ...(source.name !== source.id ? { name: source.name } : {}),
318− ...(source.namespace && source.namespace !== source.id
319− ? { namespace: source.namespace }
320− : {}),
321− ...(source.enabled ? {} : { enabled: false }),
322− connection: {
323− endpoint: source.endpoint,
324− },
325− binding: {
326− transport:
327− (source.binding.transport as "auto" | "streamable-http" | "sse" | null | undefined)
328− ?? null,
329− queryParams:
330− (source.binding.queryParams as Record<string, string> | null | undefined)
331− ?? null,
332− headers:
333− (source.binding.headers as Record<string, string> | null | undefined)
334− ?? null,
335− },
336− };
337− default:
338− throw new Error(`Unsupported source kind in test fixture: ${source.kind}`);
339− }
340−};
341−
342−const seedLocalWorkspaceSources = async (input: {
343− runtimeLocalWorkspace: RuntimeLocalWorkspaceState;
344− fixtures: ReadonlyArray<{
345− source: Source;
346− materialization: {
347− manifestJson: string | null;
348− manifestHash: string | null;
349− sourceHash: string | null;
350− documents: ReadonlyArray<any>;
351− schemaBundles: ReadonlyArray<any>;
352− operations: ReadonlyArray<StoredSourceRecipeOperationRecord>;
353− };
354− }>;
355−}) => {
356− await Effect.runPromise(writeProjectLocalExecutorConfig({
357− context: input.runtimeLocalWorkspace.context,
358− config: {
359− sources: Object.fromEntries(
360− input.fixtures.map(({ source }) => [source.id, configSourceFromSource(source)]),
361− ),
362− },
363− }));
364−
365− await Promise.all(
366− input.fixtures.map(({ source, materialization }) =>
367− Effect.runPromise(writeLocalSourceArtifact({
368− context: input.runtimeLocalWorkspace.context,
369− sourceId: source.id,
370− artifact: buildLocalSourceArtifact({
371− source,
372− materialization,
373− }),
374− })),
375− ),
376− );
377−
378− await Effect.runPromise(writeLocalWorkspaceState({
379− context: input.runtimeLocalWorkspace.context,
380− state: {
381− version: 1,
382− sources: Object.fromEntries(
383− input.fixtures.map(({ source }) => [
384− source.id,
385− {
386− status: source.status,
387− lastError: source.lastError,
388− sourceHash: source.sourceHash,
389− createdAt: source.createdAt,
390− updatedAt: source.updatedAt,
391− },
392− ]),
393− ),
394− policies: {},
395− },
396− }));
397−};
398−
399−describe("source-recipes-runtime", () => {
400− describe("recipe tool helpers", () => {
401− it("derives tool paths from explicit and implicit namespaces", () => {
402− const source = makeSource();
403− const operation = makeOperation();
404−
405− expect(recipeToolPath({
406− source,
407− operation,
408− })).toBe("github.getRepo");
409− expect(recipeToolPath({
410− source: makeSource({
411− namespace: null,
412− name: "My GitHub",
413− }),
414− operation,
415− })).toBe("my.github.getRepo");
416− expect(recipeToolPath({
417− source: makeSource({
418− namespace: "",
419− }),
420− operation,
421− })).toBe("getRepo");
422− });
423−
424− it("derives search namespaces from path segments and graphql namespaces", () => {
425− const openApiSource = makeSource();
426− const graphqlSource = makeSource({
427− kind: "graphql",
428− namespace: "issues",
429− });
430−
431− expect(recipeToolSearchNamespace({
432− source: openApiSource,
433− path: "github.getRepo",
434− operation: makeOperation(),
435− })).toBe("github.getRepo");
436− expect(recipeToolSearchNamespace({
437− source: openApiSource,
438− path: "a.b.c",
439− operation: makeOperation(),
440− })).toBe("a.b");
441− expect(recipeToolSearchNamespace({
442− source: graphqlSource,
443− path: "ignored.path",
444− operation: makeGraphqlOperation(),
445− })).toBe("issues");
446− });
447−
448− it("computes interaction modes and descriptor fields correctly", () => {
449− expect(recipeToolDescriptor({
450− source: makeSource(),
451− operation: makeOperation(),
452− path: "github.getRepo",
453− includeSchemas: true,
454− }).interaction).toBe("auto");
455−
456− expect(recipeToolDescriptor({
457− source: makeSource(),
458− operation: makeOperation({
459− operationKind: "delete",
460− }),
461− path: "github.deleteRepo",
462− includeSchemas: true,
463− }).interaction).toBe("required");
464−
465− expect(recipeToolDescriptor({
466− source: makeSource({
467− kind: "graphql",
468− }),
469− operation: makeGraphqlOperation(),
470− path: "graphql.viewer",
471− includeSchemas: true,
472− }).interaction).toBe("auto");
473−
474− expect(recipeToolDescriptor({
475− source: makeSource({
476− kind: "graphql",
477− }),
478− operation: makeGraphqlOperation({
479− operationKind: "write",
480− }),
481− path: "graphql.createIssue",
482− includeSchemas: true,
483− }).interaction).toBe("required");
484−
485− expect(recipeToolDescriptor({
486− source: makeSource({
487− kind: "mcp",
488− binding: {
489− transport: "streamable-http",
490− queryParams: null,
491− headers: null,
492− },
493− }),
494− operation: makeMcpOperation(),
495− path: "mcp.echo",
496− includeSchemas: true,
497− }).interaction).toBe("auto");
498−
499− const descriptor = recipeToolDescriptor({
500− source: makeSource(),
501− operation: makeOperation({
502− description: null,
503− title: "Fallback Title",
504− providerDataJson: null,
505− }),
506− path: "github.getRepo",
507− includeSchemas: false,
508− });
509−
510− expect(descriptor.description).toBe("Fallback Title");
511− expect(descriptor.inputSchemaJson).toBeUndefined();
512− expect(descriptor.outputSchemaJson).toBeUndefined();
513− expect(descriptor).not.toHaveProperty("providerDataJson");
514− });
515−
516− it("expands recipes into lower-cased searchable tools and handles empty operation sets", () => {
517− const recipe: LoadedSourceRecipe = {
518− source: makeSource({
519− name: "GITHUB API",
520− }),
521− sourceRecord: {
522− id: SourceIdSchema.make("src_runtime_recipe"),
523− workspaceId: WorkspaceIdSchema.make("ws_runtime_recipe"),
524− recipeId: SourceRecipeIdSchema.make("src_recipe_runtime"),
525− recipeRevisionId: SourceRecipeRevisionIdSchema.make("src_recipe_rev_runtime"),
526− name: "GITHUB API",
527− kind: "openapi",
528− endpoint: "https://api.github.com",
529− status: "connected",
530− enabled: true,
531− namespace: "github",
532− importAuthPolicy: "reuse_runtime",
533− bindingConfigJson: openApiBindingConfigJson("https://api.github.com/openapi.json"),
534− sourceHash: null,
535− lastError: null,
536− createdAt: 1000,
537− updatedAt: 1000,
538− },
539− revision: {
540− id: SourceRecipeRevisionIdSchema.make("src_recipe_rev_runtime"),
541− recipeId: SourceRecipeIdSchema.make("src_recipe_runtime"),
542− revisionNumber: 1,
543− sourceConfigJson: "{}",
544− manifestJson: null,
545− manifestHash: null,
546− materializationHash: null,
547− createdAt: 1000,
548− updatedAt: 1000,
549− },
550− documents: [],
551− schemaBundles: [],
552− operations: [makeOperation({
553− searchText: "",
554− })],
555− manifest: null,
556− };
557−
558− const expanded = Effect.runSync(
559− expandRecipeTools({
560− recipes: [recipe],
561− includeSchemas: false,
562− }),
563− );
564− expect(expanded).toHaveLength(1);
565− expect(expanded[0]?.searchText).toBe(
566− "github.getrepo github.getrepo github api github.getrepo getrepo get repo read a repository repos_getrepo repos.getrepo get /repos/{owner}/{repo} repos getrepo repos",
567− );
568−
569− expect(Effect.runSync(expandRecipeTools({
570− recipes: [{
571− ...recipe,
572− operations: [],
573− }],
574− includeSchemas: false,
575− }))).toEqual([]);
576− });
577− });
578−
579− describe("recipe loading", () => {
580− it("loads multiple sources sharing the same recipe revision", async () => {
581− const persistence = await makePersistence();
582− try {
583− const workspaceId = WorkspaceIdSchema.make("ws_shared_revision");
584− const runtimeLocalWorkspace =
585− await makeRuntimeLocalWorkspaceState(workspaceId);
586− const openApiDocument = JSON.stringify({
587− openapi: "3.0.3",
588− info: {
589− title: "GitHub",
590− version: "1.0.0",
591− },
592− paths: {
593− "/repos/{owner}/{repo}": {
594− get: {
595− operationId: "repos.getRepo",
596− parameters: [
597− {
598− name: "owner",
599− in: "path",
600− required: true,
601− schema: { type: "string" },
602− },
603− {
604− name: "repo",
605− in: "path",
606− required: true,
607− schema: { type: "string" },
608− },
609− ],
610− responses: {
611− 200: {
612− description: "ok",
613− },
614− },
615− },
616− },
617− },
618− });
619− const manifest = await Effect.runPromise(extractOpenApiManifest("GitHub", openApiDocument));
620− const definition = compileOpenApiToolDefinitions(manifest)[0]!;
621− const presentation = buildOpenApiToolPresentation({
622− definition,
623− });
624−
625− await seedLocalWorkspaceSources({
626− runtimeLocalWorkspace,
627− fixtures: ["GitHub One", "GitHub Two"].map((name, index) => ({
628− source: makeSource({
629− id: SourceIdSchema.make(`src_shared_revision_${index}`),
630− workspaceId,
631− name,
632− sourceHash: manifest.sourceHash,
633− createdAt: 1000 + index,
634− updatedAt: 1000 + index,
635− }),
636− materialization: {
637− manifestJson: JSON.stringify(manifest),
638− manifestHash: manifest.sourceHash,
639− sourceHash: manifest.sourceHash,
640− documents: [{
641− id: `src_recipe_doc_shared_revision_${index}`,
642− recipeRevisionId: SourceRecipeRevisionIdSchema.make("src_recipe_rev_materialization"),
643− documentKind: "openapi",
644− documentKey: "https://api.github.com/openapi.json",
645− contentText: openApiDocument,
646− contentHash: manifest.sourceHash,
647− fetchedAt: 1000,
648− createdAt: 1000,
649− updatedAt: 1000,
650− }],
651− schemaBundles: [],
652− operations: [makeOperation({
653− id: `src_recipe_op_shared_revision_${index}`,
654− recipeRevisionId: SourceRecipeRevisionIdSchema.make("src_recipe_rev_materialization"),
655− operationKey: definition.toolId,
656− toolId: definition.toolId,
657− title: definition.name,
658− description: definition.description,
659− searchText: `${definition.toolId} ${definition.name}`.toLowerCase(),
660− inputSchemaJson: presentation.inputSchemaJson ?? null,
661− outputSchemaJson: presentation.outputSchemaJson ?? null,
662− providerDataJson: presentation.providerDataJson,
663− })],
664− },
665− })),
666− });
667−
668− const recipes = await Effect.runPromise(
669− withRuntimeLocalWorkspace(
670− loadWorkspaceSourceRecipes({
671− rows: persistence.rows,
672− workspaceId,
673− }),
674− runtimeLocalWorkspace,
675− ),
676− );
677−
678− expect(recipes).toHaveLength(2);
679− expect(recipes[0]?.documents).toHaveLength(1);
680− expect(recipes[0]?.operations).toHaveLength(1);
681− expect(recipes[0]?.documents[0]?.contentText).toBe(
682− recipes[1]?.documents[0]?.contentText,
683− );
684− expect(recipes[0]?.operations[0]?.providerDataJson).toBe(
685− recipes[1]?.operations[0]?.providerDataJson,
686− );
687− } finally {
688− await persistence.close();
689− }
690− }, 60_000);
691−
692− it("loads sources with empty recipe documents and operations", async () => {
693− const persistence = await makePersistence();
694− try {
695− const workspaceId = WorkspaceIdSchema.make("ws_empty_recipe_rows");
696− const sourceId = SourceIdSchema.make("src_empty_recipe_rows");
697− const runtimeLocalWorkspace =
698− await makeRuntimeLocalWorkspaceState(workspaceId);
699−
700− await seedLocalWorkspaceSources({
701− runtimeLocalWorkspace,
702− fixtures: [{
703− source: makeSource({
704− id: sourceId,
705− workspaceId,
706− name: "GraphQL Demo",
707− kind: "graphql",
708− endpoint: "https://example.com/graphql",
709− namespace: "graphql",
710− binding: {
711− defaultHeaders: null,
712− },
713− }),
714− materialization: {
715− manifestJson: null,
716− manifestHash: null,
717− sourceHash: null,
718− documents: [],
719− schemaBundles: [],
720− operations: [],
721− },
722− }],
723− });
724−
725− const recipes = await Effect.runPromise(
726− withRuntimeLocalWorkspace(
727− loadWorkspaceSourceRecipes({
728− rows: persistence.rows,
729− workspaceId,
730− }),
731− runtimeLocalWorkspace,
732− ),
733− );
734−
735− expect(recipes).toHaveLength(1);
736− expect(recipes[0]?.documents).toEqual([]);
737− expect(recipes[0]?.operations).toEqual([]);
738− expect(recipes[0]?.manifest).toBeNull();
739− } finally {
740− await persistence.close();
741− }
742− }, 60_000);
743−
744− it("fails clearly when loading a missing source, missing revision, or invalid manifest", async () => {
745− const persistence = await makePersistence();
746− try {
747− const missingWorkspaceId = WorkspaceIdSchema.make("ws_missing_source");
748− const missingRuntimeLocalWorkspace =
749− await makeRuntimeLocalWorkspaceState(missingWorkspaceId);
750−
751− await expect(
752− Effect.runPromise(
753− withRuntimeLocalWorkspace(
754− loadSourceWithRecipe({
755− rows: persistence.rows,
756− workspaceId: missingWorkspaceId,
757− sourceId: SourceIdSchema.make("src_missing_source"),
758− }),
759− missingRuntimeLocalWorkspace,
760− ),
761− ),
762− ).rejects.toThrow("Source not found");
763−
764− const workspaceId = WorkspaceIdSchema.make("ws_bad_recipe_runtime");
765− const sourceId = SourceIdSchema.make("src_bad_recipe_runtime");
766− const runtimeLocalWorkspace =
767− await makeRuntimeLocalWorkspaceState(workspaceId);
768−
769− await Effect.runPromise(writeProjectLocalExecutorConfig({
770− context: runtimeLocalWorkspace.context,
771− config: {
772− sources: {
773− [sourceId]: configSourceFromSource(
774− makeSource({
775− id: sourceId,
776− workspaceId,
777− name: "Broken GitHub",
778− }),
779− ),
780− },
781− },
782− }));
783− await Effect.runPromise(writeLocalWorkspaceState({
784− context: runtimeLocalWorkspace.context,
785− state: {
786− version: 1,
787− sources: {
788− [sourceId]: {
789− status: "connected",
790− lastError: null,
791− sourceHash: null,
792− createdAt: 1000,
793− updatedAt: 1000,
794− },
795− },
796− policies: {},
797− },
798− }));
799−
800− await expect(
801− Effect.runPromise(
802− withRuntimeLocalWorkspace(
803− loadSourceWithRecipe({
804− rows: persistence.rows,
805− workspaceId,
806− sourceId,
807− }),
808− runtimeLocalWorkspace,
809− ),
810− ),
811− ).rejects.toThrow("Recipe artifact missing");
812−
813− const invalidManifestSource = makeSource({
814− id: sourceId,
815− workspaceId,
816− name: "Broken GitHub",
817− });
818− const invalidArtifact = buildLocalSourceArtifact({
819− source: invalidManifestSource,
820− materialization: {
821− manifestJson: "{}",
822− manifestHash: "manifest_hash_invalid",
823− sourceHash: "manifest_hash_invalid",
824− documents: [],
825− schemaBundles: [],
826− operations: [],
827− },
828− });
829− await Effect.runPromise(writeLocalSourceArtifact({
830− context: runtimeLocalWorkspace.context,
831− sourceId,
832− artifact: {
833− ...invalidArtifact,
834− revision: {
835− ...invalidArtifact.revision,
836− manifestJson: "{bad-json",
837− },
838− },
839− }));
840−
841− await expect(
842− Effect.runPromise(
843− withRuntimeLocalWorkspace(
844− loadSourceWithRecipe({
845− rows: persistence.rows,
846− workspaceId,
847− sourceId,
848− }),
849− runtimeLocalWorkspace,
850− ),
851− ),
852− ).rejects.toThrow(`Invalid OpenAPI manifest for ${sourceId}`);
853− } finally {
854− await persistence.close();
855− }
856− });
857− });
858−});
packages/control-plane/src/runtime/source-recipes-runtime.ts+28−15
@@ -1,7 +1,7 @@
11 import {
2+ type ToolCatalogEntry,
23 type ToolDescriptor,
34 } from "@executor/codemode-core";
4−import type { SqlControlPlaneRows } from "#persistence";
55 import type {
66 AccountId,
77 Source,
@@ -13,19 +13,20 @@ import type { · 5 unmodified lines
1313 WorkspaceId,
1414 } from "#schema";
1515 import * as Effect from "effect/Effect";
16−
17−import {
18− readLocalSourceArtifact,
19−} from "./local-source-artifacts";
2016 import { LocalSourceArtifactMissingError } from "./local-errors";
2117 import {
2218 requireRuntimeLocalWorkspace,
2319 } from "./local-runtime-context";
20+import {
21+ type WorkspaceStorageServices,
22+ SourceArtifactStore,
23+} from "./local-storage";
2424 import { namespaceFromSourceName } from "./source-names";
2525 import {
2626 getSourceAdapterForOperation,
2727 getSourceAdapterForSource,
2828 } from "./source-adapters";
29+import type { ControlPlaneStoreShape } from "./store";
2930 import type { SourceAdapterPersistedOperationMetadata } from "./source-adapters/types";
3031 import { firstSchemaBundle } from "./source-adapters/shared";
3132 import { loadSourceById, loadSourcesInWorkspace } from "./source-store";
@@ -68,6 +69,16 @@ export type LoadedSourceRecipeToolIndexEntry = { · 36 unmodified lines
6869 descriptor: ToolDescriptor;
6970 };
7071
72+export const recipeToolCatalogEntry = (input: {
73+ tool: LoadedSourceRecipeToolIndexEntry;
74+ score: (queryTokens: readonly string[]) => number;
75+}): ToolCatalogEntry => ({
76+ descriptor: input.tool.descriptor,
77+ namespace: input.tool.searchNamespace,
78+ searchText: input.tool.searchText,
79+ score: input.score,
80+});
81+
7182 const parseManifestForRecipe = (input: {
7283 source: Source;
7384 revision: StoredSourceRecipeRevisionRecord;
@@ -157,18 +168,19 @@ const primarySchemaBundleForRevision = (input: { · 83 unmodified lines
157168 };
158169
159170 export const loadWorkspaceSourceRecipes = (input: {
160− rows: SqlControlPlaneRows;
171+ rows: ControlPlaneStoreShape;
161172 workspaceId: WorkspaceId;
162173 actorAccountId?: AccountId | null;
163−}): Effect.Effect<readonly LoadedSourceRecipe[], Error, never> =>
174+}): Effect.Effect<readonly LoadedSourceRecipe[], Error, WorkspaceStorageServices> =>
164175 Effect.gen(function* () {
165176 const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(input.workspaceId);
177+ const sourceArtifactStore = yield* SourceArtifactStore;
166178 const sources = yield* loadSourcesInWorkspace(input.rows, input.workspaceId, {
167179 actorAccountId: input.actorAccountId,
168180 });
169181 const localRecipes = yield* Effect.forEach(sources, (source) =>
170182 Effect.gen(function* () {
171− const artifact = yield* readLocalSourceArtifact({
183+ const artifact = yield* sourceArtifactStore.read({
172184 context: runtimeLocalWorkspace.context,
173185 sourceId: source.id,
174186 });
@@ -215,19 +227,20 @@ export const loadWorkspaceSourceRecipes = (input: { · 40 unmodified lines
215227 });
216228
217229 export const loadSourceWithRecipe = (input: {
218− rows: SqlControlPlaneRows;
230+ rows: ControlPlaneStoreShape;
219231 workspaceId: WorkspaceId;
220232 sourceId: Source["id"];
221233 actorAccountId?: AccountId | null;
222−}): Effect.Effect<LoadedSourceRecipe, Error | LocalSourceArtifactMissingError, never> =>
234+}): Effect.Effect<LoadedSourceRecipe, Error | LocalSourceArtifactMissingError, WorkspaceStorageServices> =>
223235 Effect.gen(function* () {
224236 const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(input.workspaceId);
237+ const sourceArtifactStore = yield* SourceArtifactStore;
225238 const source = yield* loadSourceById(input.rows, {
226239 workspaceId: input.workspaceId,
227240 sourceId: input.sourceId,
228241 actorAccountId: input.actorAccountId,
229242 });
230− const artifact = yield* readLocalSourceArtifact({
243+ const artifact = yield* sourceArtifactStore.read({
231244 context: runtimeLocalWorkspace.context,
232245 sourceId: source.id,
233246 });
@@ -335,11 +348,11 @@ export const expandRecipeTools = (input: { · 101 unmodified lines
335348 );
336349
337350 export const loadWorkspaceSourceRecipeToolIndex = (input: {
338− rows: SqlControlPlaneRows;
351+ rows: ControlPlaneStoreShape;
339352 workspaceId: WorkspaceId;
340353 actorAccountId?: AccountId | null;
341354 includeSchemas: boolean;
342−}): Effect.Effect<readonly LoadedSourceRecipeToolIndexEntry[], Error, never> =>
355+}): Effect.Effect<readonly LoadedSourceRecipeToolIndexEntry[], Error, WorkspaceStorageServices> =>
343356 Effect.gen(function* () {
344357 const recipes = yield* loadWorkspaceSourceRecipes({
345358 rows: input.rows,
@@ -364,12 +377,12 @@ export const loadWorkspaceSourceRecipeToolIndex = (input: { · 18 unmodified lines
364377 });
365378
366379 export const loadWorkspaceSourceRecipeToolByPath = (input: {
367− rows: SqlControlPlaneRows;
380+ rows: ControlPlaneStoreShape;
368381 workspaceId: WorkspaceId;
369382 path: string;
370383 actorAccountId?: AccountId | null;
371384 includeSchemas: boolean;
372−}): Effect.Effect<LoadedSourceRecipeToolIndexEntry | null, Error, never> =>
385+}): Effect.Effect<LoadedSourceRecipeToolIndexEntry | null, Error, WorkspaceStorageServices> =>
373386 Effect.gen(function* () {
374387 const recipes = yield* loadWorkspaceSourceRecipes({
375388 rows: input.rows,
packages/control-plane/src/runtime/source-store.test.ts+0−310
@@ -1,310 +0,0 @@
1−import { mkdtempSync } from "node:fs";
2−import { tmpdir } from "node:os";
3−import { join } from "node:path";
4−
5−import { describe, expect, it } from "@effect/vitest";
6−
7−import {
8− AccountIdSchema,
9− decodeBuiltInAuthArtifactConfig,
10− McpSourceAuthSessionDataJsonSchema,
11− SecretMaterialIdSchema,
12− SourceAuthSessionIdSchema,
13− SourceIdSchema,
14− WorkspaceIdSchema,
15− type Source,
16−} from "#schema";
17−import * as Effect from "effect/Effect";
18−import * as Option from "effect/Option";
19−import * as Schema from "effect/Schema";
20−import * as Scope from "effect/Scope";
21−
22−import {
23− createSqlControlPlanePersistence,
24− type SqlControlPlanePersistence,
25−} from "../persistence";
26−import {
27− loadLocalExecutorConfig,
28− resolveLocalWorkspaceContext,
29−} from "./local-config";
30−import {
31− RuntimeLocalWorkspaceService,
32− type RuntimeLocalWorkspaceState,
33−} from "./local-runtime-context";
34−import {
35− loadLocalWorkspaceState,
36−} from "./local-workspace-state";
37−import { persistSource, removeSourceById } from "./source-store";
38−
39−const makePersistence: Effect.Effect<SqlControlPlanePersistence, unknown, Scope.Scope> =
40− Effect.acquireRelease(
41− createSqlControlPlanePersistence({
42− localDataDir: ":memory:",
43− }),
44− (persistence) =>
45− Effect.tryPromise({
46− try: () => persistence.close(),
47− catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))),
48− }).pipe(Effect.orDie),
49− );
50−
51−const encodeSessionDataJson = Schema.encodeSync(McpSourceAuthSessionDataJsonSchema);
52−
53−const makeRuntimeLocalWorkspace = (input: {
54− workspaceId: Source["workspaceId"];
55− accountId: ReturnType<typeof AccountIdSchema.make>;
56−}) =>
57− Effect.gen(function* () {
58− const workspaceRoot = mkdtempSync(join(tmpdir(), "executor-source-store-"));
59− const context = yield* resolveLocalWorkspaceContext({ workspaceRoot });
60− const loadedConfig = yield* loadLocalExecutorConfig(context);
61−
62− return {
63− context,
64− installation: {
65− workspaceId: input.workspaceId,
66− accountId: input.accountId,
67− },
68− loadedConfig,
69− } satisfies RuntimeLocalWorkspaceState;
70− });
71−
72−const withRuntimeLocalWorkspace = <A, E>(
73− effect: Effect.Effect<A, E, never>,
74− runtimeLocalWorkspace: RuntimeLocalWorkspaceState,
75−) =>
76− effect.pipe(
77− Effect.provideService(RuntimeLocalWorkspaceService, runtimeLocalWorkspace),
78− );
79−
80−const makeOpenApiSource = (input: {
81− workspaceId: Source["workspaceId"];
82− sourceId: Source["id"];
83− now: number;
84− updatedAt?: number;
85− name?: string;
86− endpoint?: string;
87− specUrl?: string;
88− auth: Source["auth"];
89−}): Source => ({
90− id: input.sourceId,
91− workspaceId: input.workspaceId,
92− name: input.name ?? "GitHub",
93− kind: "openapi",
94− endpoint: input.endpoint ?? "https://api.github.com",
95− status: "connected",
96− enabled: true,
97− namespace: "github",
98− bindingVersion: 1,
99− binding: {
100− specUrl: input.specUrl ?? "https://example.com/openapi.json",
101− defaultHeaders: null,
102− },
103− importAuthPolicy: "reuse_runtime",
104− importAuth: { kind: "none" },
105− auth: input.auth,
106− sourceHash: null,
107− lastError: null,
108− createdAt: input.now,
109− updatedAt: input.updatedAt ?? input.now,
110−});
111−
112−describe("source-store", () => {
113− it.scoped("replaces superseded secrets and removes source auth state cleanly", () =>
114− Effect.gen(function* () {
115− const persistence = yield* makePersistence;
116− const now = Date.now();
117− const accountId = AccountIdSchema.make("acc_source_store");
118− const workspaceId = WorkspaceIdSchema.make("ws_source_store");
119− const sourceId = SourceIdSchema.make("github");
120− const firstTokenId = SecretMaterialIdSchema.make("sec_source_store_first");
121− const secondTokenId = SecretMaterialIdSchema.make("sec_source_store_second");
122− const runtimeLocalWorkspace = yield* makeRuntimeLocalWorkspace({
123− workspaceId,
124− accountId,
125− });
126−
127− yield* persistence.rows.secretMaterials.upsert({
128− id: firstTokenId,
129− name: null,
130− purpose: "auth_material",
131− value: "ghp_first",
132− createdAt: now,
133− updatedAt: now,
134− });
135− yield* persistence.rows.secretMaterials.upsert({
136− id: secondTokenId,
137− name: null,
138− purpose: "auth_material",
139− value: "ghp_second",
140− createdAt: now,
141− updatedAt: now,
142− });
143−
144− yield* withRuntimeLocalWorkspace(
145− persistSource(
146− persistence.rows,
147− makeOpenApiSource({
148− workspaceId,
149− sourceId,
150− now,
151− auth: {
152− kind: "bearer",
153− headerName: "Authorization",
154− prefix: "Bearer ",
155− token: {
156− providerId: "postgres",
157− handle: firstTokenId,
158− },
159− },
160− }),
161− ),
162− runtimeLocalWorkspace,
163− );
164−
165− yield* persistence.rows.sourceAuthSessions.upsert({
166− id: SourceAuthSessionIdSchema.make("src_auth_source_store"),
167− workspaceId,
168− sourceId,
169− actorAccountId: accountId,
170− executionId: null,
171− interactionId: null,
172− providerKind: "mcp_oauth",
173− credentialSlot: "runtime",
174− status: "pending",
175− state: "state_source_store",
176− sessionDataJson: encodeSessionDataJson({
177− kind: "mcp_oauth",
178− endpoint: "https://api.github.com",
179− redirectUri: "http://127.0.0.1/callback",
180− scope: null,
181− resourceMetadataUrl: null,
182− authorizationServerUrl: null,
183− resourceMetadata: null,
184− authorizationServerMetadata: null,
185− clientInformation: null,
186− codeVerifier: "verifier",
187− authorizationUrl: "https://example.com/auth",
188− }),
189− errorText: null,
190− completedAt: null,
191− createdAt: now,
192− updatedAt: now,
193− });
194−
195− yield* withRuntimeLocalWorkspace(
196− persistSource(
197− persistence.rows,
198− makeOpenApiSource({
199− workspaceId,
200− sourceId,
201− now,
202− updatedAt: now + 1,
203− auth: {
204− kind: "bearer",
205− headerName: "Authorization",
206− prefix: "Bearer ",
207− token: {
208− providerId: "postgres",
209− handle: secondTokenId,
210− },
211− },
212− }),
213− ),
214− runtimeLocalWorkspace,
215− );
216−
217− expect(Option.isNone(yield* persistence.rows.secretMaterials.getById(firstTokenId))).toBe(true);
218− const authArtifacts = yield* persistence.rows.authArtifacts.listByWorkspaceId(workspaceId);
219− expect(authArtifacts).toHaveLength(1);
220− const decoded = authArtifacts[0] ? decodeBuiltInAuthArtifactConfig(authArtifacts[0]) : null;
221− expect(
222− decoded !== null && decoded.artifactKind === "static_bearer"
223− ? decoded.config.token.handle
224− : null,
225− ).toBe(secondTokenId);
226−
227− const removed = yield* withRuntimeLocalWorkspace(
228− removeSourceById(persistence.rows, {
229− workspaceId,
230− sourceId,
231− }),
232− runtimeLocalWorkspace,
233− );
234− expect(removed).toBe(true);
235−
236− expect(Option.isNone(yield* persistence.rows.secretMaterials.getById(secondTokenId))).toBe(true);
237− expect(yield* persistence.rows.authArtifacts.listByWorkspaceId(workspaceId)).toHaveLength(0);
238− expect(yield* persistence.rows.sourceAuthSessions.listByWorkspaceId(workspaceId)).toHaveLength(0);
239− const config = yield* loadLocalExecutorConfig(runtimeLocalWorkspace.context);
240− expect(config.config?.sources?.[sourceId]).toBeUndefined();
241− const workspaceState = yield* loadLocalWorkspaceState(runtimeLocalWorkspace.context);
242− expect(workspaceState.sources[sourceId]).toBeUndefined();
243− }),
244− );
245−
246− it.scoped("creates a fresh actor-scoped credential when a shared credential already exists", () =>
247− Effect.gen(function* () {
248− const persistence = yield* makePersistence;
249− const now = Date.now();
250− const accountId = AccountIdSchema.make("acc_actor_scoped");
251− const workspaceId = WorkspaceIdSchema.make("ws_actor_scoped");
252− const sourceId = SourceIdSchema.make("github");
253− const sharedTokenId = SecretMaterialIdSchema.make("sec_actor_scoped_shared");
254− const runtimeLocalWorkspace = yield* makeRuntimeLocalWorkspace({
255− workspaceId,
256− accountId,
257− });
258− yield* persistence.rows.secretMaterials.upsert({
259− id: sharedTokenId,
260− name: null,
261− purpose: "auth_material",
262− value: "ghp_shared",
263− createdAt: now,
264− updatedAt: now,
265− });
266−
267− const source = makeOpenApiSource({
268− workspaceId,
269− sourceId,
270− now,
271− auth: {
272− kind: "bearer",
273− headerName: "Authorization",
274− prefix: "Bearer ",
275− token: {
276− providerId: "postgres",
277− handle: sharedTokenId,
278− },
279− },
280− });
281−
282− yield* withRuntimeLocalWorkspace(
283− persistSource(persistence.rows, source),
284− runtimeLocalWorkspace,
285− );
286− yield* withRuntimeLocalWorkspace(
287− persistSource(
288− persistence.rows,
289− {
290− ...source,
291− updatedAt: now + 1,
292− },
293− {
294− actorAccountId: accountId,
295− },
296− ),
297− runtimeLocalWorkspace,
298− );
299−
300− const authArtifacts = yield* persistence.rows.authArtifacts.listByWorkspaceAndSourceId({
301− workspaceId,
302− sourceId,
303− });
304− expect(authArtifacts).toHaveLength(2);
305− expect(authArtifacts.some((artifact) => artifact.actorAccountId === null)).toBe(true);
306− expect(authArtifacts.some((artifact) => artifact.actorAccountId === accountId)).toBe(true);
307− expect(new Set(authArtifacts.map((artifact) => artifact.id)).size).toBe(2);
308− }),
309− );
310−});
packages/control-plane/src/runtime/source-store.ts+49−29
@@ -9,7 +9,6 @@ import type { · 8 unmodified lines
99 WorkspaceId,
1010 } from "#schema";
1111 import { SourceIdSchema } from "#schema";
12−import { type SqlControlPlaneRows } from "#persistence";
1312 import * as Effect from "effect/Effect";
1413
1514 import {
@@ -21,15 +20,9 @@ import { · 5 unmodified lines
2120 sourceAuthFromAuthArtifact,
2221 } from "./auth-artifacts";
2322 import {
24− loadLocalExecutorConfig,
2523 type LoadedLocalExecutorConfig,
2624 type ResolvedLocalWorkspaceContext,
27− writeProjectLocalExecutorConfig,
2825 } from "./local-config";
29−import {
30− readLocalSourceArtifact,
31− removeLocalSourceArtifact,
32−} from "./local-source-artifacts";
3326 import {
3427 LocalConfiguredSourceNotFoundError,
3528 LocalExecutorConfigDecodeError,
@@ -42,9 +35,18 @@ import { · 6 unmodified lines
4235 import {
4336 requireRuntimeLocalWorkspace,
4437 } from "./local-runtime-context";
38+import type {
39+ SourceArtifactStoreShape,
40+ WorkspaceStorageServices,
41+ WorkspaceConfigStoreShape,
42+ WorkspaceStateStoreShape,
43+} from "./local-storage";
44+import {
45+ SourceArtifactStore,
46+ WorkspaceConfigStore,
47+ WorkspaceStateStore,
48+} from "./local-storage";
4549 import {
46− loadLocalWorkspaceState,
47− writeLocalWorkspaceState,
4850 type LocalWorkspaceState,
4951 } from "./local-workspace-state";
5052 import {
@@ -54,13 +56,14 @@ import { · 3 unmodified lines
5456 import { createDefaultSecretMaterialDeleter } from "./secret-material-providers";
5557 import { authArtifactSecretMaterialRefs } from "./auth-artifacts";
5658 import { removeAuthLeaseAndSecrets } from "./auth-leases";
59+import type { ControlPlaneStoreShape } from "./store";
5760 import { getSourceAdapter } from "./source-adapters";
5861 import { slugify } from "./slug";
5962
6063 const secretRefKey = (ref: { providerId: string; handle: string }): string =>
6164 `${ref.providerId}:${ref.handle}`;
6265
63−const cleanupAuthArtifactSecretRefs = (rows: SqlControlPlaneRows, input: {
66+const cleanupAuthArtifactSecretRefs = (rows: ControlPlaneStoreShape, input: {
6467 previous: AuthArtifact | null;
6568 next: AuthArtifact | null;
6669 }) =>
@@ -246,6 +249,9 @@ const resolveRuntimeLocalWorkspace = (workspaceId: WorkspaceId): Effect.Effect<{ · 179 unmodified lines
246249 workspaceId: WorkspaceId;
247250 accountId: AccountId;
248251 };
252+ workspaceConfigStore: WorkspaceConfigStoreShape;
253+ workspaceStateStore: WorkspaceStateStoreShape;
254+ sourceArtifactStore: SourceArtifactStoreShape;
249255 loadedConfig: LoadedLocalExecutorConfig;
250256 workspaceState: LocalWorkspaceState;
251257 },
@@ -253,16 +259,27 @@ const resolveRuntimeLocalWorkspace = (workspaceId: WorkspaceId): Effect.Effect<{ · 1 unmodified line
253259 | RuntimeLocalWorkspaceMismatchError
254260 | LocalFileSystemError
255261 | LocalExecutorConfigDecodeError
256− | LocalWorkspaceStateDecodeError,
257−never> =>
262+ | LocalWorkspaceStateDecodeError
263+ | Error,
264+WorkspaceStorageServices> =>
258265 Effect.gen(function* () {
259266 const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(workspaceId);
260− const loadedConfig = yield* loadLocalExecutorConfig(runtimeLocalWorkspace.context);
261− const workspaceState = yield* loadLocalWorkspaceState(runtimeLocalWorkspace.context);
267+ const workspaceConfigStore = yield* WorkspaceConfigStore;
268+ const workspaceStateStore = yield* WorkspaceStateStore;
269+ const sourceArtifactStore = yield* SourceArtifactStore;
270+ const loadedConfig = yield* workspaceConfigStore.load(
271+ runtimeLocalWorkspace.context,
272+ );
273+ const workspaceState = yield* workspaceStateStore.load(
274+ runtimeLocalWorkspace.context,
275+ );
262276
263277 return {
264278 context: runtimeLocalWorkspace.context,
265279 installation: runtimeLocalWorkspace.installation,
280+ workspaceConfigStore,
281+ workspaceStateStore,
282+ sourceArtifactStore,
266283 loadedConfig,
267284 workspaceState,
268285 };
@@ -270,6 +287,7 @@ never> => · 1 unmodified line
270287
271288 const buildLocalSourceRecord = (input: {
272289 context: ResolvedLocalWorkspaceContext;
290+ sourceArtifactStore: SourceArtifactStoreShape;
273291 workspaceId: WorkspaceId;
274292 loadedConfig: LoadedLocalExecutorConfig;
275293 workspaceState: LocalWorkspaceState;
@@ -317,7 +335,7 @@ const buildLocalSourceRecord = (input: { · 41 unmodified lines
317335 updatedAt: existingState?.updatedAt ?? Date.now(),
318336 });
319337
320− const artifact = yield* readLocalSourceArtifact({
338+ const artifact = yield* input.sourceArtifactStore.read({
321339 context: input.context,
322340 sourceId: input.sourceId,
323341 }).pipe(
@@ -379,7 +397,7 @@ const buildLocalSourceRecord = (input: { · 55 unmodified lines
379397 });
380398
381399 export const loadSourcesInWorkspace = (
382− rows: SqlControlPlaneRows,
400+ rows: ControlPlaneStoreShape,
383401 workspaceId: WorkspaceId,
384402 options: {
385403 actorAccountId?: AccountId | null;
@@ -393,7 +411,7 @@ export const loadSourcesInWorkspace = ( · 7 unmodified lines
393411 | LocalWorkspaceStateDecodeError
394412 | LocalConfiguredSourceNotFoundError
395413 | Error,
396− never
414+ WorkspaceStorageServices
397415 > =>
398416 Effect.gen(function* () {
399417 const localWorkspace = yield* resolveRuntimeLocalWorkspace(workspaceId);
@@ -404,6 +422,7 @@ export const loadSourcesInWorkspace = ( · 4 unmodified lines
404422 Effect.map(
405423 buildLocalSourceRecord({
406424 context: localWorkspace.context,
425+ sourceArtifactStore: localWorkspace.sourceArtifactStore,
407426 workspaceId,
408427 loadedConfig: localWorkspace.loadedConfig,
409428 workspaceState: localWorkspace.workspaceState,
@@ -416,7 +435,7 @@ export const loadSourcesInWorkspace = ( · 6 unmodified lines
416435 );
417436 });
418437
419−export const loadSourceById = (rows: SqlControlPlaneRows, input: {
438+export const loadSourceById = (rows: ControlPlaneStoreShape, input: {
420439 workspaceId: WorkspaceId;
421440 sourceId: Source["id"];
422441 actorAccountId?: AccountId | null;
@@ -429,7 +448,7 @@ export const loadSourceById = (rows: SqlControlPlaneRows, input: { · 6 unmodified lines
429448 | LocalWorkspaceStateDecodeError
430449 | LocalConfiguredSourceNotFoundError
431450 | Error,
432− never
451+ WorkspaceStorageServices
433452 > =>
434453 Effect.gen(function* () {
435454 const localWorkspace = yield* resolveRuntimeLocalWorkspace(input.workspaceId);
@@ -445,6 +464,7 @@ export const loadSourceById = (rows: SqlControlPlaneRows, input: { · 9 unmodified lines
445464
446465 const localSource = yield* buildLocalSourceRecord({
447466 context: localWorkspace.context,
467+ sourceArtifactStore: localWorkspace.sourceArtifactStore,
448468 workspaceId: input.workspaceId,
449469 loadedConfig: localWorkspace.loadedConfig,
450470 workspaceState: localWorkspace.workspaceState,
@@ -514,7 +534,7 @@ const configSourceFromLocalSource = (input: { · 63 unmodified lines
514534 }
515535 };
516536
517−const removeAuthArtifactsForSource = (rows: SqlControlPlaneRows, input: {
537+const removeAuthArtifactsForSource = (rows: ControlPlaneStoreShape, input: {
518538 workspaceId: WorkspaceId;
519539 sourceId: Source["id"];
520540 }) =>
@@ -551,10 +571,10 @@ const removeAuthArtifactsForSource = (rows: SqlControlPlaneRows, input: { · 30 unmodified lines
551571 return existingAuthArtifacts.length;
552572 });
553573
554−export const removeSourceById = (rows: SqlControlPlaneRows, input: {
574+export const removeSourceById = (rows: ControlPlaneStoreShape, input: {
555575 workspaceId: WorkspaceId;
556576 sourceId: Source["id"];
557−}) =>
577+}): Effect.Effect<boolean, Error, WorkspaceStorageServices> =>
558578 Effect.gen(function* () {
559579 const localWorkspace = yield* resolveRuntimeLocalWorkspace(input.workspaceId);
560580 if (!localWorkspace.loadedConfig.config?.sources?.[input.sourceId]) {
@@ -566,7 +586,7 @@ export const removeSourceById = (rows: SqlControlPlaneRows, input: { · 5 unmodified lines
566586 ...(projectConfig.sources ?? {}),
567587 };
568588 delete sources[input.sourceId];
569− yield* writeProjectLocalExecutorConfig({
589+ yield* localWorkspace.workspaceConfigStore.writeProject({
570590 context: localWorkspace.context,
571591 config: {
572592 ...projectConfig,
@@ -582,11 +602,11 @@ export const removeSourceById = (rows: SqlControlPlaneRows, input: { · 9 unmodified lines
582602 ...localWorkspace.workspaceState,
583603 sources: remainingSources,
584604 };
585− yield* writeLocalWorkspaceState({
605+ yield* localWorkspace.workspaceStateStore.write({
586606 context: localWorkspace.context,
587607 state: workspaceState,
588608 });
589− yield* removeLocalSourceArtifact({
609+ yield* localWorkspace.sourceArtifactStore.remove({
590610 context: localWorkspace.context,
591611 sourceId: input.sourceId,
592612 });
@@ -605,12 +625,12 @@ export const removeSourceById = (rows: SqlControlPlaneRows, input: { · 12 unmodified lines
605625 });
606626
607627 export const persistSource = (
608− rows: SqlControlPlaneRows,
628+ rows: ControlPlaneStoreShape,
609629 source: Source,
610630 options: {
611631 actorAccountId?: AccountId | null;
612632 } = {},
613−) =>
633+): Effect.Effect<Source, Error, WorkspaceStorageServices> =>
614634 Effect.gen(function* () {
615635 const localWorkspace = yield* resolveRuntimeLocalWorkspace(source.workspaceId);
616636 const nextSource = {
@@ -648,7 +668,7 @@ export const persistSource = ( · 31 unmodified lines
648668 existingConfigAuth: existingConfigSource?.connection.auth,
649669 config: localWorkspace.loadedConfig.config,
650670 });
651− yield* writeProjectLocalExecutorConfig({
671+ yield* localWorkspace.workspaceConfigStore.writeProject({
652672 context: localWorkspace.context,
653673 config: {
654674 ...projectConfig,
@@ -737,7 +757,7 @@ export const persistSource = ( · 82 unmodified lines
737757 },
738758 },
739759 };
740− yield* writeLocalWorkspaceState({
760+ yield* localWorkspace.workspaceStateStore.write({
741761 context: localWorkspace.context,
742762 state: workspaceState,
743763 });
packages/control-plane/src/runtime/store.ts+199−9
@@ -1,15 +1,205 @@
1−import { type SqlControlPlaneRows, SqlControlPlaneRowsService } from "#persistence";
21 import * as Context from "effect/Context";
3−import * as Effect from "effect/Effect";
4−import * as Layer from "effect/Layer";
2+import type * as Effect from "effect/Effect";
53
6−export type ControlPlaneStoreShape = SqlControlPlaneRows;
4+import type {
5+ AuthArtifact,
6+ AuthLease,
7+ Execution,
8+ ExecutionInteraction,
9+ ExecutionStep,
10+ SecretMaterial,
11+ SourceAuthSession,
12+ WorkspaceSourceOauthClient,
13+} from "#schema";
14+
15+type SecretMaterialSummary = {
16+ id: string;
17+ name: string | null;
18+ purpose: string;
19+ createdAt: number;
20+ updatedAt: number;
21+};
22+
23+export type ControlPlaneStoreShape = {
24+ authArtifacts: {
25+ listByWorkspaceId: (
26+ workspaceId: AuthArtifact["workspaceId"],
27+ ) => Effect.Effect<readonly AuthArtifact[], Error, never>;
28+ listByWorkspaceAndSourceId: (input: {
29+ workspaceId: AuthArtifact["workspaceId"];
30+ sourceId: AuthArtifact["sourceId"];
31+ }) => Effect.Effect<readonly AuthArtifact[], Error, never>;
32+ getByWorkspaceSourceAndActor: (input: {
33+ workspaceId: AuthArtifact["workspaceId"];
34+ sourceId: AuthArtifact["sourceId"];
35+ actorAccountId: AuthArtifact["actorAccountId"];
36+ slot: AuthArtifact["slot"];
37+ }) => Effect.Effect<import("effect/Option").Option<AuthArtifact>, Error, never>;
38+ upsert: (artifact: AuthArtifact) => Effect.Effect<void, Error, never>;
39+ removeByWorkspaceSourceAndActor: (input: {
40+ workspaceId: AuthArtifact["workspaceId"];
41+ sourceId: AuthArtifact["sourceId"];
42+ actorAccountId: AuthArtifact["actorAccountId"];
43+ slot?: AuthArtifact["slot"];
44+ }) => Effect.Effect<boolean, Error, never>;
45+ removeByWorkspaceAndSourceId: (input: {
46+ workspaceId: AuthArtifact["workspaceId"];
47+ sourceId: AuthArtifact["sourceId"];
48+ }) => Effect.Effect<number, Error, never>;
49+ };
50+ authLeases: {
51+ listAll: () => Effect.Effect<readonly AuthLease[], Error, never>;
52+ getByAuthArtifactId: (
53+ authArtifactId: AuthLease["authArtifactId"],
54+ ) => Effect.Effect<import("effect/Option").Option<AuthLease>, Error, never>;
55+ upsert: (lease: AuthLease) => Effect.Effect<void, Error, never>;
56+ removeByAuthArtifactId: (
57+ authArtifactId: AuthLease["authArtifactId"],
58+ ) => Effect.Effect<boolean, Error, never>;
59+ };
60+ sourceOauthClients: {
61+ getByWorkspaceSourceAndProvider: (input: {
62+ workspaceId: WorkspaceSourceOauthClient["workspaceId"];
63+ sourceId: WorkspaceSourceOauthClient["sourceId"];
64+ providerKey: string;
65+ }) => Effect.Effect<
66+ import("effect/Option").Option<WorkspaceSourceOauthClient>,
67+ Error,
68+ never
69+ >;
70+ upsert: (
71+ oauthClient: WorkspaceSourceOauthClient,
72+ ) => Effect.Effect<void, Error, never>;
73+ removeByWorkspaceAndSourceId: (input: {
74+ workspaceId: WorkspaceSourceOauthClient["workspaceId"];
75+ sourceId: WorkspaceSourceOauthClient["sourceId"];
76+ }) => Effect.Effect<number, Error, never>;
77+ };
78+ sourceAuthSessions: {
79+ listAll: () => Effect.Effect<readonly SourceAuthSession[], Error, never>;
80+ listByWorkspaceId: (
81+ workspaceId: SourceAuthSession["workspaceId"],
82+ ) => Effect.Effect<readonly SourceAuthSession[], Error, never>;
83+ getById: (
84+ id: SourceAuthSession["id"],
85+ ) => Effect.Effect<import("effect/Option").Option<SourceAuthSession>, Error, never>;
86+ getByState: (
87+ state: SourceAuthSession["state"],
88+ ) => Effect.Effect<import("effect/Option").Option<SourceAuthSession>, Error, never>;
89+ getPendingByWorkspaceSourceAndActor: (input: {
90+ workspaceId: SourceAuthSession["workspaceId"];
91+ sourceId: SourceAuthSession["sourceId"];
92+ actorAccountId: SourceAuthSession["actorAccountId"];
93+ credentialSlot?: SourceAuthSession["credentialSlot"];
94+ }) => Effect.Effect<
95+ import("effect/Option").Option<SourceAuthSession>,
96+ Error,
97+ never
98+ >;
99+ insert: (session: SourceAuthSession) => Effect.Effect<void, Error, never>;
100+ update: (
101+ id: SourceAuthSession["id"],
102+ patch: Partial<
103+ Omit<SourceAuthSession, "id" | "workspaceId" | "sourceId" | "createdAt">
104+ >,
105+ ) => Effect.Effect<import("effect/Option").Option<SourceAuthSession>, Error, never>;
106+ upsert: (session: SourceAuthSession) => Effect.Effect<void, Error, never>;
107+ removeByWorkspaceAndSourceId: (
108+ workspaceId: SourceAuthSession["workspaceId"],
109+ sourceId: SourceAuthSession["sourceId"],
110+ ) => Effect.Effect<boolean, Error, never>;
111+ };
112+ secretMaterials: {
113+ getById: (
114+ id: SecretMaterial["id"],
115+ ) => Effect.Effect<import("effect/Option").Option<SecretMaterial>, Error, never>;
116+ listAll: () => Effect.Effect<readonly SecretMaterialSummary[], Error, never>;
117+ upsert: (material: SecretMaterial) => Effect.Effect<void, Error, never>;
118+ updateById: (
119+ id: SecretMaterial["id"],
120+ update: { name?: string | null; value?: string },
121+ ) => Effect.Effect<
122+ import("effect/Option").Option<SecretMaterialSummary>,
123+ Error,
124+ never
125+ >;
126+ removeById: (id: SecretMaterial["id"]) => Effect.Effect<boolean, Error, never>;
127+ listLinkedSources: () => Effect.Effect<
128+ Map<string, Array<{ sourceId: string; sourceName: string }>>,
129+ Error,
130+ never
131+ >;
132+ };
133+ executions: {
134+ getById: (
135+ executionId: Execution["id"],
136+ ) => Effect.Effect<import("effect/Option").Option<Execution>, Error, never>;
137+ getByWorkspaceAndId: (
138+ workspaceId: Execution["workspaceId"],
139+ executionId: Execution["id"],
140+ ) => Effect.Effect<import("effect/Option").Option<Execution>, Error, never>;
141+ insert: (execution: Execution) => Effect.Effect<void, Error, never>;
142+ update: (
143+ executionId: Execution["id"],
144+ patch: Partial<
145+ Omit<Execution, "id" | "workspaceId" | "createdByAccountId" | "createdAt">
146+ >,
147+ ) => Effect.Effect<import("effect/Option").Option<Execution>, Error, never>;
148+ };
149+ executionInteractions: {
150+ getById: (
151+ interactionId: ExecutionInteraction["id"],
152+ ) => Effect.Effect<
153+ import("effect/Option").Option<ExecutionInteraction>,
154+ Error,
155+ never
156+ >;
157+ listByExecutionId: (
158+ executionId: ExecutionInteraction["executionId"],
159+ ) => Effect.Effect<readonly ExecutionInteraction[], Error, never>;
160+ getPendingByExecutionId: (
161+ executionId: ExecutionInteraction["executionId"],
162+ ) => Effect.Effect<
163+ import("effect/Option").Option<ExecutionInteraction>,
164+ Error,
165+ never
166+ >;
167+ insert: (
168+ interaction: ExecutionInteraction,
169+ ) => Effect.Effect<void, Error, never>;
170+ update: (
171+ interactionId: ExecutionInteraction["id"],
172+ patch: Partial<
173+ Omit<ExecutionInteraction, "id" | "executionId" | "createdAt">
174+ >,
175+ ) => Effect.Effect<
176+ import("effect/Option").Option<ExecutionInteraction>,
177+ Error,
178+ never
179+ >;
180+ };
181+ executionSteps: {
182+ getByExecutionAndSequence: (
183+ executionId: ExecutionStep["executionId"],
184+ sequence: ExecutionStep["sequence"],
185+ ) => Effect.Effect<import("effect/Option").Option<ExecutionStep>, Error, never>;
186+ listByExecutionId: (
187+ executionId: ExecutionStep["executionId"],
188+ ) => Effect.Effect<readonly ExecutionStep[], Error, never>;
189+ insert: (step: ExecutionStep) => Effect.Effect<void, Error, never>;
190+ deleteByExecutionId: (
191+ executionId: ExecutionStep["executionId"],
192+ ) => Effect.Effect<void, Error, never>;
193+ updateByExecutionAndSequence: (
194+ executionId: ExecutionStep["executionId"],
195+ sequence: ExecutionStep["sequence"],
196+ patch: Partial<
197+ Omit<ExecutionStep, "id" | "executionId" | "sequence" | "createdAt">
198+ >,
199+ ) => Effect.Effect<import("effect/Option").Option<ExecutionStep>, Error, never>;
200+ };
201+};
7202
8203 export class ControlPlaneStore extends Context.Tag(
9204 "#runtime/ControlPlaneStore",
10205 )<ControlPlaneStore, ControlPlaneStoreShape>() {}
11−
12−export const ControlPlaneStoreLive = Layer.effect(
13− ControlPlaneStore,
14− Effect.map(SqlControlPlaneRowsService, (rows) => rows),
15−);
packages/control-plane/src/runtime/workspace-execution-environment.test.ts+0−1986
@@ -1,1986 +0,0 @@
1−import { randomUUID } from "node:crypto";
2−import { createServer } from "node:http";
3−import { dirname, join } from "node:path";
4−
5−import {
6− FileSystem,
7− HttpApi,
8− HttpApiBuilder,
9− HttpApiEndpoint,
10− HttpApiGroup,
11− HttpApiSchema,
12− HttpServer,
13−} from "@effect/platform";
14−import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem";
15−import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
16−import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
17−import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
18−import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
19−import {
20− buildOpenApiToolPresentation,
21− compileOpenApiToolDefinitions,
22− extractOpenApiManifest,
23−} from "@executor/codemode-openapi";
24−import { describe, expect, it } from "@effect/vitest";
25−import {
26− graphql,
27− GraphQLBoolean,
28− GraphQLID,
29− GraphQLInputObjectType,
30− GraphQLInt,
31− GraphQLList,
32− GraphQLNonNull,
33− GraphQLObjectType,
34− GraphQLSchema,
35− GraphQLString,
36−} from "graphql";
37−import {
38− AccountIdSchema,
39− ExecutionIdSchema,
40− SecretMaterialIdSchema,
41− SourceIdSchema,
42− SourceRecipeSchemaBundleIdSchema,
43− WorkspaceIdSchema,
44− type Source,
45−} from "#schema";
46−import * as Context from "effect/Context";
47−import * as Effect from "effect/Effect";
48−import * as Exit from "effect/Exit";
49−import * as Layer from "effect/Layer";
50−import * as Scope from "effect/Scope";
51−import { Schema } from "effect";
52−import * as z from "zod/v4";
53−
54−import {
55− createSqlControlPlanePersistence,
56− type SqlControlPlanePersistence,
57−} from "#persistence";
58−import type { SourceId, WorkspaceId } from "#schema";
59−
60−import {
61− createRuntimeSourceAuthService,
62− type RuntimeSourceAuthService,
63−} from "./source-auth-service";
64−import {
65− loadLocalExecutorConfig,
66− resolveLocalWorkspaceContext,
67− writeProjectLocalExecutorConfig,
68−} from "./local-config";
69−import { createLiveExecutionManager } from "./live-execution";
70−import {
71− RuntimeLocalWorkspaceService,
72− type RuntimeLocalWorkspaceState,
73−} from "./local-runtime-context";
74−import {
75− buildLocalSourceArtifact,
76− readLocalSourceArtifact,
77− writeLocalSourceArtifact,
78−} from "./local-source-artifacts";
79−import {
80− loadLocalWorkspaceState,
81− writeLocalWorkspaceState,
82−} from "./local-workspace-state";
83−import { materializationFromMcpManifestEntries } from "./source-adapters/mcp";
84−import { createWorkspaceExecutionEnvironmentResolver } from "./workspace-execution-environment";
85−
86−type CountedMcpServer = {
87− endpoint: string;
88− counts: {
89− listTools: number;
90− callTool: number;
91− };
92− close: () => Promise<void>;
93−};
94−
95−type OpenApiSpecServer = {
96− baseUrl: string;
97− specUrl: string;
98− seenAuthHeaders: Array<string | null>;
99− close: () => Promise<void>;
100−};
101−
102−type GraphqlServer = {
103− endpoint: string;
104− seenAuthHeaders: Array<string | null>;
105− close: () => Promise<void>;
106−};
107−
108−const normalizeSearchText = (
109− ...parts: ReadonlyArray<string | null | undefined>
110−): string =>
111− parts
112− .flatMap((part) => (part ? [part.trim()] : []))
113− .filter(Boolean)
114− .join(" ")
115− .toLowerCase();
116−
117−const provideNodeFileSystem = <A, E, R>(
118− effect: Effect.Effect<A, E, R | FileSystem.FileSystem>,
119−): Effect.Effect<A, E, Exclude<R, FileSystem.FileSystem>> =>
120− effect.pipe(Effect.provide(NodeFileSystem.layer)) as Effect.Effect<
121− A,
122− E,
123− Exclude<R, FileSystem.FileSystem>
124− >;
125−
126−const GraphqlUserType = new GraphQLObjectType({
127− name: "User",
128− fields: {
129− id: {
130− type: new GraphQLNonNull(GraphQLID),
131− },
132− name: {
133− type: new GraphQLNonNull(GraphQLString),
134− },
135− },
136−});
137−
138−const GraphqlTeamType = new GraphQLObjectType({
139− name: "Team",
140− fields: {
141− id: {
142− type: new GraphQLNonNull(GraphQLID),
143− },
144− key: {
145− type: new GraphQLNonNull(GraphQLString),
146− },
147− name: {
148− type: new GraphQLNonNull(GraphQLString),
149− },
150− },
151−});
152−
153−const GraphqlIssueType = new GraphQLObjectType({
154− name: "Issue",
155− fields: {
156− id: {
157− type: new GraphQLNonNull(GraphQLID),
158− },
159− identifier: {
160− type: new GraphQLNonNull(GraphQLString),
161− },
162− title: {
163− type: new GraphQLNonNull(GraphQLString),
164− },
165− team: {
166− type: new GraphQLNonNull(GraphqlTeamType),
167− },
168− },
169−});
170−
171−const GraphqlPageInfoType = new GraphQLObjectType({
172− name: "PageInfo",
173− fields: {
174− hasNextPage: {
175− type: new GraphQLNonNull(GraphQLBoolean),
176− },
177− endCursor: {
178− type: GraphQLString,
179− },
180− },
181−});
182−
183−const GraphqlIssueConnectionType = new GraphQLObjectType({
184− name: "IssueConnection",
185− fields: {
186− nodes: {
187− type: new GraphQLNonNull(
188− new GraphQLList(new GraphQLNonNull(GraphqlIssueType)),
189− ),
190− },
191− pageInfo: {
192− type: new GraphQLNonNull(GraphqlPageInfoType),
193− },
194− },
195−});
196−
197−const GraphqlCreateIssueInputType = new GraphQLInputObjectType({
198− name: "CreateIssueInput",
199− fields: {
200− title: {
201− type: new GraphQLNonNull(GraphQLString),
202− },
203− teamId: {
204− type: GraphQLID,
205− },
206− },
207−});
208−
209−const GraphqlQueryType = new GraphQLObjectType({
210− name: "Query",
211− fields: {
212− viewer: {
213− type: new GraphQLNonNull(GraphqlUserType),
214− description: "The current user",
215− resolve: () => ({
216− id: "usr_123",
217− name: "Rhys",
218− }),
219− },
220− issue: {
221− type: GraphqlIssueType,
222− description: "Find a single issue by id",
223− args: {
224− id: {
225− type: new GraphQLNonNull(GraphQLID),
226− },
227− },
228− resolve: (_source, args: { id: string }) => ({
229− id: args.id,
230− identifier: "ENG-123",
231− title: "Investigate GraphQL support",
232− team: {
233− id: "team_eng",
234− key: "ENG",
235− name: "Engineering",
236− },
237− }),
238− },
239− issues: {
240− type: new GraphQLNonNull(GraphqlIssueConnectionType),
241− description: "List issues for the workspace",
242− args: {
243− first: {
244− type: GraphQLInt,
245− },
246− query: {
247− type: GraphQLString,
248− },
249− },
250− resolve: () => ({
251− nodes: [
252− {
253− id: "iss_1",
254− identifier: "ENG-123",
255− title: "Investigate GraphQL support",
256− team: {
257− id: "team_eng",
258− key: "ENG",
259− name: "Engineering",
260− },
261− },
262− {
263− id: "iss_2",
264− identifier: "ENG-124",
265− title: "Ship GraphQL field tools",
266− team: {
267− id: "team_eng",
268− key: "ENG",
269− name: "Engineering",
270− },
271− },
272− ],
273− pageInfo: {
274− hasNextPage: false,
275− endCursor: "iss_2",
276− },
277− }),
278− },
279− },
280−});
281−
282−const GraphqlMutationType = new GraphQLObjectType({
283− name: "Mutation",
284− fields: {
285− noop: {
286− type: GraphQLString,
287− resolve: () => "ok",
288− },
289− createIssue: {
290− type: new GraphQLNonNull(GraphqlIssueType),
291− description: "Create a new issue",
292− args: {
293− input: {
294− type: new GraphQLNonNull(GraphqlCreateIssueInputType),
295− },
296− },
297− resolve: (
298− _source,
299− args: { input: { title: string; teamId?: string | null } },
300− ) => ({
301− id: "iss_created",
302− identifier: "ENG-999",
303− title: args.input.title,
304− team: {
305− id: args.input.teamId ?? "team_eng",
306− key: "ENG",
307− name: "Engineering",
308− },
309− }),
310− },
311− },
312−});
313−
314−const testGraphqlSchema = new GraphQLSchema({
315− query: GraphqlQueryType,
316− mutation: GraphqlMutationType,
317−});
318−
319−const closeScope = (scope: Scope.CloseableScope) =>
320− Scope.close(scope, Exit.void).pipe(Effect.orDie);
321−
322−const makePersistence: Effect.Effect<
323− SqlControlPlanePersistence,
324− unknown,
325− Scope.Scope
326−> = Effect.acquireRelease(
327− createSqlControlPlanePersistence({
328− localDataDir: ":memory:",
329− }),
330− (persistence) =>
331− Effect.tryPromise({
332− try: () => persistence.close(),
333− catch: (cause) =>
334− cause instanceof Error ? cause : new Error(String(cause)),
335− }).pipe(Effect.orDie),
336−);
337−
338−const makeCountedMcpServer = Effect.acquireRelease(
339− Effect.promise<CountedMcpServer>(
340− () =>
341− new Promise<CountedMcpServer>((resolve, reject) => {
342− const counts = {
343− listTools: 0,
344− callTool: 0,
345− };
346− let closed = false;
347− const app = createMcpExpressApp({ host: "127.0.0.1" });
348−
349− const createServer = () => {
350− const server = new McpServer(
351− {
352− name: "workspace-execution-environment-test-server",
353− version: "1.0.0",
354− },
355− {
356− capabilities: {
357− tools: {},
358− },
359− },
360− );
361−
362− server.registerTool(
363− "echo",
364− {
365− description: "Echo the provided string",
366− inputSchema: {
367− value: z.string(),
368− },
369− },
370− async ({ value }: { value: string }) => ({
371− content: [{ type: "text", text: `echo:${value}` }],
372− }),
373− );
374−
375− return server;
376− };
377−
378− const countMethods = (body: unknown) => {
379− const payloads = Array.isArray(body) ? body : [body];
380− for (const payload of payloads) {
381− if (!payload || typeof payload !== "object") {
382− continue;
383− }
384−
385− const method = "method" in payload ? payload.method : undefined;
386− if (method === "tools/list") {
387− counts.listTools += 1;
388− } else if (method === "tools/call") {
389− counts.callTool += 1;
390− }
391− }
392− };
393−
394− const handle = async (req: any, res: any, parsedBody?: unknown) => {
395− countMethods(parsedBody);
396−
397− const server = createServer();
398− const transport = new StreamableHTTPServerTransport({
399− sessionIdGenerator: undefined,
400− });
401−
402− try {
403− await server.connect(transport);
404− await transport.handleRequest(req, res, parsedBody);
405− } finally {
406− await transport.close().catch(() => undefined);
407− await server.close().catch(() => undefined);
408− }
409− };
410−
411− app.post("/mcp", async (req: any, res: any) => {
412− await handle(req, res, req.body);
413− });
414−
415− app.get("/mcp", async (req: any, res: any) => {
416− await handle(req, res);
417− });
418−
419− app.delete("/mcp", async (req: any, res: any) => {
420− await handle(req, res, req.body);
421− });
422−
423− const listener = app.listen(0, "127.0.0.1", () => {
424− const address = listener.address();
425− if (!address || typeof address === "string") {
426− reject(new Error("failed to resolve MCP test server address"));
427− return;
428− }
429−
430− resolve({
431− endpoint: `http://127.0.0.1:${address.port}/mcp`,
432− counts,
433− close: async () => {
434− if (closed) {
435− return;
436− }
437− closed = true;
438−
439− await new Promise<void>((closeResolve, closeReject) => {
440− listener.close((error: Error | undefined) => {
441− if (error) {
442− closeReject(error);
443− return;
444− }
445− closeResolve();
446− });
447− });
448− },
449− });
450− });
451−
452− listener.once("error", reject);
453− }),
454− ),
455− (server) =>
456− Effect.tryPromise({
457− try: () => server.close(),
458− catch: (error: unknown) =>
459− error instanceof Error ? error : new Error(String(error)),
460− }).pipe(Effect.orDie),
461−);
462−
463−const ownerParam = HttpApiSchema.param("owner", Schema.String);
464−const repoParam = HttpApiSchema.param("repo", Schema.String);
465−
466−class GeneratedReposApi extends HttpApiGroup.make("repos").add(
467− HttpApiEndpoint.get("getRepo")`/repos/${ownerParam}/${repoParam}`.addSuccess(
468− Schema.Struct({
469− ok: Schema.Boolean,
470− full_name: Schema.String,
471− }),
472− ),
473−) {}
474−
475−class GeneratedApi extends HttpApi.make("generated").add(GeneratedReposApi) {}
476−
477−const makeOpenApiSpecServer = Effect.acquireRelease(
478− Effect.gen(function* () {
479− const seenAuthHeaders: OpenApiSpecServer["seenAuthHeaders"] = [];
480− const handlersLayer = HttpApiBuilder.group(
481− GeneratedApi,
482− "repos",
483− (handlers) =>
484− handlers.handle("getRepo", ({ path, request }) =>
485− Effect.sync(() => {
486− seenAuthHeaders.push(
487− typeof request.headers.authorization === "string"
488− ? request.headers.authorization

1498 more lines aren’t shown here.

packages/control-plane/src/runtime/workspace-execution-environment.ts+164−266

This diff is large: its 614 lines aren’t shown here.

packages/control-plane/src/schema/models/auth-artifact.ts+25−31

This diff is large: its 117 lines aren’t shown here.

packages/control-plane/src/schema/models/auth-lease.ts+2−9

This diff is large: its 31 lines aren’t shown here.

packages/control-plane/src/schema/models/auth/account.ts+12−17

This diff is large: its 47 lines aren’t shown here.

packages/control-plane/src/schema/models/code-migration.ts+5−8

This diff is large: its 20 lines aren’t shown here.

packages/control-plane/src/schema/models/execution.ts+46−55

This diff is large: its 160 lines aren’t shown here.

packages/control-plane/src/schema/models/secret-material.ts+3−9

This diff is large: its 30 lines aren’t shown here.

packages/control-plane/src/schema/models/source-auth-session.ts+5−9

This diff is large: its 37 lines aren’t shown here.

packages/control-plane/src/schema/models/source-oauth-client.ts+12−12

This diff is large: its 37 lines aren’t shown here.

packages/control-plane/src/schema/models/source-recipe.ts+34−45

This diff is large: its 126 lines aren’t shown here.

packages/control-plane/src/schema/models/source.ts+11−10

This diff is large: its 57 lines aren’t shown here.

packages/control-plane/tsconfig.json+4−0

This diff is large: its 10 lines aren’t shown here.

packages/kitchen-sink/src/source-runtime.test.ts+2−2

This diff is large: its 18 lines aren’t shown here.

packages/react/src/index.test.tsx+0−1

This diff is large: its 8 lines aren’t shown here.

packages/server/src/config.ts+0−1

This diff is large: its 8 lines aren’t shown here.

packages/server/src/index.ts+0−4

This diff is large: its 25 lines aren’t shown here.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t