History

Use config keys as local source ids

Rhys Sullivan committed 8 months ago

Commit e05b2641 parent 9fe77d820 files · +80 −185Browse files

Unified · Split

20 files
  1. packages/control-plane/src/persistence/control-plane-rows.test.ts +0 −4
  2. packages/control-plane/src/persistence/post-migrations.test.ts +0 −1
  3. packages/control-plane/src/runtime/control-plane-runtime.test.ts +2 −2
  4. packages/control-plane/src/runtime/local-source-artifacts.ts +5 −8
  5. packages/control-plane/src/runtime/local-workspace-state.ts +0 −2
  6. packages/control-plane/src/runtime/local-workspace-sync.ts +9 −12
  7. packages/control-plane/src/runtime/source-auth-material.test.ts +0 −1
  8. packages/control-plane/src/runtime/source-auth-service.test.ts +0 −1
  9. packages/control-plane/src/runtime/source-definitions.test.ts +0 −1
  10. packages/control-plane/src/runtime/source-definitions.ts +0 −3
  11. packages/control-plane/src/runtime/source-inspection.test.ts +0 −2
  12. packages/control-plane/src/runtime/source-materialization.ts +6 −12
  13. packages/control-plane/src/runtime/source-recipes-runtime.test.ts +0 −5
  14. packages/control-plane/src/runtime/source-recipes-runtime.ts +2 −13
  15. packages/control-plane/src/runtime/source-store.test.ts +0 −1
  16. packages/control-plane/src/runtime/source-store.ts +56 −110
  17. packages/control-plane/src/runtime/workspace-execution-environment.test.ts +0 −2
  18. packages/control-plane/src/schema/models/source.ts +0 −3
  19. packages/kitchen-sink/src/source-runtime.test.ts +0 −1
  20. packages/react/src/index.ts +0 −1
packages/control-plane/src/persistence/control-plane-rows.test.ts+0−4
@@ -140,7 +140,6 @@ const seedWorkspaceSourceState = (input: { · 139 unmodified lines
140 yield* input.persistence.rows.sources.insert({140 yield* input.persistence.rows.sources.insert({
141 id: input.sourceId,141 id: input.sourceId,
142 workspaceId: input.workspaceId,142 workspaceId: input.workspaceId,
143 configKey: null,
144 recipeId,143 recipeId,
145 recipeRevisionId,144 recipeRevisionId,
146 name: "Github",145 name: "Github",
@@ -278,7 +277,6 @@ describe("control-plane-persistence-drizzle", () => { · 131 unmodified lines
278 yield* persistence.rows.sources.insert({277 yield* persistence.rows.sources.insert({
279 id: SourceIdSchema.make("src_1"),278 id: SourceIdSchema.make("src_1"),
280 workspaceId,279 workspaceId,
281 configKey: null,
282 recipeId: SourceRecipeIdSchema.make("src_recipe_1"),280 recipeId: SourceRecipeIdSchema.make("src_recipe_1"),
283 recipeRevisionId: SourceRecipeRevisionIdSchema.make("src_recipe_rev_1"),281 recipeRevisionId: SourceRecipeRevisionIdSchema.make("src_recipe_rev_1"),
284 name: "Github",282 name: "Github",
@@ -863,7 +861,6 @@ describe("control-plane-persistence-drizzle", () => { · 578 unmodified lines
863 yield* persistence.rows.sources.insert({861 yield* persistence.rows.sources.insert({
864 id: SourceIdSchema.make("src_shared_removed"),862 id: SourceIdSchema.make("src_shared_removed"),
865 workspaceId: removedWorkspaceId,863 workspaceId: removedWorkspaceId,
866 configKey: null,
867 recipeId: sharedRecipeId,864 recipeId: sharedRecipeId,
868 recipeRevisionId: sharedRecipeRevisionId,865 recipeRevisionId: sharedRecipeRevisionId,
869 name: "Github Removed",866 name: "Github Removed",
@@ -882,7 +879,6 @@ describe("control-plane-persistence-drizzle", () => { · 12 unmodified lines
882 yield* persistence.rows.sources.insert({879 yield* persistence.rows.sources.insert({
883 id: SourceIdSchema.make("src_shared_remaining"),880 id: SourceIdSchema.make("src_shared_remaining"),
884 workspaceId: remainingWorkspaceId,881 workspaceId: remainingWorkspaceId,
885 configKey: null,
886 recipeId: sharedRecipeId,882 recipeId: sharedRecipeId,
887 recipeRevisionId: sharedRecipeRevisionId,883 recipeRevisionId: sharedRecipeRevisionId,
888 name: "Github Remaining",884 name: "Github Remaining",
packages/control-plane/src/persistence/post-migrations.test.ts+0−1
@@ -188,7 +188,6 @@ const seedMigratedSourceRecipe = (input: { · 187 unmodified lines
188 yield* input.persistence.rows.sources.insert({188 yield* input.persistence.rows.sources.insert({
189 id: input.sourceId,189 id: input.sourceId,
190 workspaceId: input.workspaceId,190 workspaceId: input.workspaceId,
191 configKey: null,
192 recipeId,191 recipeId,
193 recipeRevisionId,192 recipeRevisionId,
194 name: input.kind === "openapi" ? "GitHub" : "GraphQL Demo",193 name: input.kind === "openapi" ? "GitHub" : "GraphQL Demo",
packages/control-plane/src/runtime/control-plane-runtime.test.ts+2−2
@@ -325,7 +325,7 @@ describe("control-plane-runtime", () => { · 324 unmodified lines
325 const runtime = yield* makeRuntime;325 const runtime = yield* makeRuntime;
326 const installation = runtime.localInstallation;326 const installation = runtime.localInstallation;
327 const executionId = ExecutionIdSchema.make("exec_local_credential");327 const executionId = ExecutionIdSchema.make("exec_local_credential");
328 const sourceId = SourceIdSchema.make("src_local_credential");328 const sourceId = SourceIdSchema.make("github");
329 const interactionSuffix = "executor.sources.add:test";329 const interactionSuffix = "executor.sources.add:test";
330 const interactionId = ExecutionInteractionIdSchema.make(330 const interactionId = ExecutionInteractionIdSchema.make(
331 `${executionId}:${interactionSuffix}`,331 `${executionId}:${interactionSuffix}`,
@@ -488,7 +488,7 @@ describe("control-plane-runtime", () => { · 156 unmodified lines
488 const runtime = yield* makeRuntime;488 const runtime = yield* makeRuntime;
489 const installation = runtime.localInstallation;489 const installation = runtime.localInstallation;
490 const executionId = ExecutionIdSchema.make("exec_local_credential_continue");490 const executionId = ExecutionIdSchema.make("exec_local_credential_continue");
491 const sourceId = SourceIdSchema.make("src_local_credential_continue");491 const sourceId = SourceIdSchema.make("github");
492 const interactionSuffix = "executor.sources.add:continue";492 const interactionSuffix = "executor.sources.add:continue";
493 const interactionId = ExecutionInteractionIdSchema.make(493 const interactionId = ExecutionInteractionIdSchema.make(
494 `${executionId}:${interactionSuffix}`,494 `${executionId}:${interactionSuffix}`,
packages/control-plane/src/runtime/local-source-artifacts.ts+5−8
@@ -29,7 +29,6 @@ const LOCAL_SOURCE_ARTIFACT_VERSION = 1 as const; · 28 unmodified lines
29export const LocalSourceArtifactSchema = Schema.Struct({29export const LocalSourceArtifactSchema = Schema.Struct({
30 version: Schema.Literal(LOCAL_SOURCE_ARTIFACT_VERSION),30 version: Schema.Literal(LOCAL_SOURCE_ARTIFACT_VERSION),
31 sourceId: SourceIdSchema,31 sourceId: SourceIdSchema,
32 configKey: Schema.String,
33 recipeId: SourceRecipeIdSchema,32 recipeId: SourceRecipeIdSchema,
34 generatedAt: TimestampMsSchema,33 generatedAt: TimestampMsSchema,
35 revision: StoredSourceRecipeRevisionRecordSchema,34 revision: StoredSourceRecipeRevisionRecordSchema,
@@ -44,12 +43,12 @@ const decodeLocalSourceArtifact = Schema.decodeUnknownSync(LocalSourceArtifactSc · 8 unmodified lines
44 43
45const localSourceArtifactPath = (input: {44const localSourceArtifactPath = (input: {
46 context: ResolvedLocalWorkspaceContext;45 context: ResolvedLocalWorkspaceContext;
47 configKey: string;46 sourceId: string;
48}): string =>47}): string =>
49 join(48 join(
50 input.context.artifactsDirectory,49 input.context.artifactsDirectory,
51 "sources",50 "sources",
52 `${input.configKey}.json`,51 `${input.sourceId}.json`,
53 );52 );
54 53
55const canonicalMaterializationHash = (input: {54const canonicalMaterializationHash = (input: {
@@ -112,7 +111,6 @@ const bindRevisionId = <T extends { recipeRevisionId: string }>( · 56 unmodified lines
112 111
113export const buildLocalSourceArtifact = (input: {112export const buildLocalSourceArtifact = (input: {
114 source: Source;113 source: Source;
115 configKey: string;
116 materialization: SourceRecipeMaterialization;114 materialization: SourceRecipeMaterialization;
117}): LocalSourceArtifact => {115}): LocalSourceArtifact => {
118 const recipeId: SourceRecipeId = stableSourceRecipeId(input.source);116 const recipeId: SourceRecipeId = stableSourceRecipeId(input.source);
@@ -131,7 +129,6 @@ export const buildLocalSourceArtifact = (input: { · 12 unmodified lines
131 return {129 return {
132 version: LOCAL_SOURCE_ARTIFACT_VERSION,130 version: LOCAL_SOURCE_ARTIFACT_VERSION,
133 sourceId: input.source.id,131 sourceId: input.source.id,
134 configKey: input.configKey,
135 recipeId,132 recipeId,
136 generatedAt: now,133 generatedAt: now,
137 revision,134 revision,
@@ -143,7 +140,7 @@ export const buildLocalSourceArtifact = (input: { · 5 unmodified lines
143 140
144export const readLocalSourceArtifact = async (input: {141export const readLocalSourceArtifact = async (input: {
145 context: ResolvedLocalWorkspaceContext;142 context: ResolvedLocalWorkspaceContext;
146 configKey: string;143 sourceId: string;
147}): Promise<LocalSourceArtifact | null> => {144}): Promise<LocalSourceArtifact | null> => {
148 const path = localSourceArtifactPath(input);145 const path = localSourceArtifactPath(input);
149 146
@@ -166,7 +163,7 @@ export const readLocalSourceArtifact = async (input: { · 16 unmodified lines
166 163
167export const writeLocalSourceArtifact = async (input: {164export const writeLocalSourceArtifact = async (input: {
168 context: ResolvedLocalWorkspaceContext;165 context: ResolvedLocalWorkspaceContext;
169 configKey: string;166 sourceId: string;
170 artifact: LocalSourceArtifact;167 artifact: LocalSourceArtifact;
171}): Promise<void> => {168}): Promise<void> => {
172 const path = localSourceArtifactPath(input);169 const path = localSourceArtifactPath(input);
@@ -178,7 +175,7 @@ export const writeLocalSourceArtifact = async (input: { · 5 unmodified lines
178 175
179export const removeLocalSourceArtifact = async (input: {176export const removeLocalSourceArtifact = async (input: {
180 context: ResolvedLocalWorkspaceContext;177 context: ResolvedLocalWorkspaceContext;
181 configKey: string;178 sourceId: string;
182}): Promise<void> => {179}): Promise<void> => {
183 const path = localSourceArtifactPath(input);180 const path = localSourceArtifactPath(input);
184 181
packages/control-plane/src/runtime/local-workspace-state.ts+0−2
@@ -3,7 +3,6 @@ import { join } from "node:path"; · 2 unmodified lines
3 3
4import {4import {
5 PolicyIdSchema,5 PolicyIdSchema,
6 SourceIdSchema,
7 SourceStatusSchema,6 SourceStatusSchema,
8 TimestampMsSchema,7 TimestampMsSchema,
9} from "#schema";8} from "#schema";
@@ -14,7 +13,6 @@ import type { ResolvedLocalWorkspaceContext } from "./local-config"; · 4 unmodified lines
14const WORKSPACE_STATE_BASENAME = "workspace-state.json";13const WORKSPACE_STATE_BASENAME = "workspace-state.json";
15 14
16const LocalWorkspaceSourceStateSchema = Schema.Struct({15const LocalWorkspaceSourceStateSchema = Schema.Struct({
17 id: SourceIdSchema,
18 status: SourceStatusSchema,16 status: SourceStatusSchema,
19 lastError: Schema.NullOr(Schema.String),17 lastError: Schema.NullOr(Schema.String),
20 sourceHash: Schema.NullOr(Schema.String),18 sourceHash: Schema.NullOr(Schema.String),
packages/control-plane/src/runtime/local-workspace-sync.ts+9−12
@@ -42,12 +42,11 @@ const toApprovalMode = (approval: LocalConfigPolicy["approval"]): Policy["approv · 41 unmodified lines
42 approval === "manual" ? "required" : "auto";42 approval === "manual" ? "required" : "auto";
43 43
44export const deriveSourceConfigKey = (44export const deriveSourceConfigKey = (
45 source: Pick<Source, "configKey" | "namespace" | "name">,45 source: Pick<Source, "namespace" | "name">,
46 used: Set<string>,46 used: Set<string>,
47): string => {47): string => {
48 const base =48 const base =
49 trimOrNull(source.configKey)49 trimOrNull(source.namespace)
50 ?? trimOrNull(source.namespace)
51 ?? trimOrNull(source.name)50 ?? trimOrNull(source.name)
52 ?? "source";51 ?? "source";
53 const slugBase = slugify(base) || "source";52 const slugBase = slugify(base) || "source";
@@ -112,14 +111,15 @@ const configAuthFromStoredSource = (input: { · 58 unmodified lines
112 111
113export const configSourceFromStoredSource = (input: {112export const configSourceFromStoredSource = (input: {
114 source: Source;113 source: Source;
114 sourceId: string;
115 config: LocalExecutorConfig | null;115 config: LocalExecutorConfig | null;
116}): LocalConfigSource => {116}): LocalConfigSource => {
117 const auth = configAuthFromStoredSource(input);117 const auth = configAuthFromStoredSource(input);
118 const common = {118 const common = {
119 ...(trimOrNull(input.source.name) !== trimOrNull(input.source.configKey)119 ...(trimOrNull(input.source.name) !== trimOrNull(input.sourceId)
120 ? { name: input.source.name }120 ? { name: input.source.name }
121 : {}),121 : {}),
122 ...(trimOrNull(input.source.namespace) !== trimOrNull(input.source.configKey)122 ...(trimOrNull(input.source.namespace) !== trimOrNull(input.sourceId)
123 ? { namespace: input.source.namespace ?? undefined }123 ? { namespace: input.source.namespace ?? undefined }
124 : {}),124 : {}),
125 ...(input.source.enabled === false ? { enabled: false } : {}),125 ...(input.source.enabled === false ? { enabled: false } : {}),
@@ -231,10 +231,8 @@ const exportWorkspaceConfig = (input: { · 105 unmodified lines
231 const configKey = deriveSourceConfigKey(source, sourceKeys);231 const configKey = deriveSourceConfigKey(source, sourceKeys);
232 sourceMappings.push({ configKey, source });232 sourceMappings.push({ configKey, source });
233 sourcesConfig[configKey] = configSourceFromStoredSource({233 sourcesConfig[configKey] = configSourceFromStoredSource({
234 source: {234 source,
235 ...source,235 sourceId: configKey,
236 configKey,
237 },
238 config: input.loadedConfig.config,236 config: input.loadedConfig.config,
239 });237 });
240 }238 }
@@ -304,7 +302,6 @@ const exportWorkspaceConfig = (input: { · 63 unmodified lines
304 sources: {302 sources: {
305 ...workspaceState.sources,303 ...workspaceState.sources,
306 [configKey]: {304 [configKey]: {
307 id: source.id,
308 status: source.status,305 status: source.status,
309 lastError: source.lastError,306 lastError: source.lastError,
310 sourceHash: source.sourceHash,307 sourceHash: source.sourceHash,
@@ -324,13 +321,13 @@ const exportWorkspaceConfig = (input: { · 13 unmodified lines
324 try: () =>321 try: () =>
325 writeLocalSourceArtifact({322 writeLocalSourceArtifact({
326 context: input.context,323 context: input.context,
327 configKey,324 sourceId: configKey,
328 artifact: buildLocalSourceArtifact({325 artifact: buildLocalSourceArtifact({
329 source: {326 source: {
330 ...source,327 ...source,
328 id: configKey as Source["id"],
331 sourceHash: revision.manifestHash ?? source.sourceHash,329 sourceHash: revision.manifestHash ?? source.sourceHash,
332 },330 },
333 configKey,
334 materialization: {331 materialization: {
335 manifestJson: revision.manifestJson,332 manifestJson: revision.manifestJson,
336 manifestHash: revision.manifestHash,333 manifestHash: revision.manifestHash,
packages/control-plane/src/runtime/source-auth-material.test.ts+0−1
@@ -12,7 +12,6 @@ import { resolveSourceAuthMaterial } from "./source-auth-material"; · 11 unmodified lines
12const makeSource = (overrides: Partial<Source> = {}): Source => ({12const makeSource = (overrides: Partial<Source> = {}): Source => ({
13 id: SourceIdSchema.make("src_tool_artifacts"),13 id: SourceIdSchema.make("src_tool_artifacts"),
14 workspaceId: WorkspaceIdSchema.make("ws_tool_artifacts"),14 workspaceId: WorkspaceIdSchema.make("ws_tool_artifacts"),
15 configKey: null,
16 name: "GitHub",15 name: "GitHub",
17 kind: "openapi",16 kind: "openapi",
18 endpoint: "https://api.github.com",17 endpoint: "https://api.github.com",
packages/control-plane/src/runtime/source-auth-service.test.ts+0−1
@@ -32,7 +32,6 @@ import { resolveSourceAuthMaterial } from "./source-auth-material"; · 31 unmodified lines
32const makeExistingOpenApiSource = (auth: Source["auth"]): Source => ({32const makeExistingOpenApiSource = (auth: Source["auth"]): Source => ({
33 id: "src_test" as Source["id"],33 id: "src_test" as Source["id"],
34 workspaceId: "ws_test" as Source["workspaceId"],34 workspaceId: "ws_test" as Source["workspaceId"],
35 configKey: null,
36 name: "GitHub",35 name: "GitHub",
37 kind: "openapi",36 kind: "openapi",
38 endpoint: "https://api.github.com",37 endpoint: "https://api.github.com",
packages/control-plane/src/runtime/source-definitions.test.ts+0−1
@@ -44,7 +44,6 @@ const mcpBinding = (input: { · 43 unmodified lines
44const makeSource = (overrides: Partial<Source> = {}): Source => ({44const makeSource = (overrides: Partial<Source> = {}): Source => ({
45 id: SourceIdSchema.make("src_source_definitions"),45 id: SourceIdSchema.make("src_source_definitions"),
46 workspaceId: WorkspaceIdSchema.make("ws_source_definitions"),46 workspaceId: WorkspaceIdSchema.make("ws_source_definitions"),
47 configKey: null,
48 name: "GitHub",47 name: "GitHub",
49 kind: "openapi",48 kind: "openapi",
50 endpoint: "https://api.github.com",49 endpoint: "https://api.github.com",
packages/control-plane/src/runtime/source-definitions.ts+0−3
@@ -232,7 +232,6 @@ export const createSourceFromPayload = (input: { · 231 unmodified lines
232 return yield* validateSourceByKind({232 return yield* validateSourceByKind({
233 id: input.sourceId,233 id: input.sourceId,
234 workspaceId: input.workspaceId,234 workspaceId: input.workspaceId,
235 configKey: null,
236 name: input.payload.name.trim(),235 name: input.payload.name.trim(),
237 kind: input.payload.kind,236 kind: input.payload.kind,
238 endpoint: input.payload.endpoint.trim(),237 endpoint: input.payload.endpoint.trim(),
@@ -353,7 +352,6 @@ export const splitSourceForStorage = (input: { · 114 unmodified lines
353 const sourceRecord: StoredSourceRecord = {352 const sourceRecord: StoredSourceRecord = {
354 id: input.source.id,353 id: input.source.id,
355 workspaceId: input.source.workspaceId,354 workspaceId: input.source.workspaceId,
356 configKey: input.source.configKey,
357 recipeId: input.recipeId,355 recipeId: input.recipeId,
358 recipeRevisionId: input.recipeRevisionId,356 recipeRevisionId: input.recipeRevisionId,
359 name: input.source.name,357 name: input.source.name,
@@ -408,7 +406,6 @@ export const projectSourceFromStorage = (input: { · 48 unmodified lines
408 return {406 return {
409 id: input.sourceRecord.id,407 id: input.sourceRecord.id,
410 workspaceId: input.sourceRecord.workspaceId,408 workspaceId: input.sourceRecord.workspaceId,
411 configKey: input.sourceRecord.configKey,
412 name: input.sourceRecord.name,409 name: input.sourceRecord.name,
413 kind: input.sourceRecord.kind,410 kind: input.sourceRecord.kind,
414 endpoint: input.sourceRecord.endpoint,411 endpoint: input.sourceRecord.endpoint,
packages/control-plane/src/runtime/source-inspection.test.ts+0−2
@@ -62,7 +62,6 @@ const makeSource = (input: { · 61 unmodified lines
62}): Source => ({62}): Source => ({
63 id: input.sourceId,63 id: input.sourceId,
64 workspaceId: input.workspaceId,64 workspaceId: input.workspaceId,
65 configKey: null,
66 name: "Cloudflare API",65 name: "Cloudflare API",
67 kind: "openapi",66 kind: "openapi",
68 endpoint: "https://api.cloudflare.com/client/v4",67 endpoint: "https://api.cloudflare.com/client/v4",
@@ -238,7 +237,6 @@ describe("source-inspection", () => { · 169 unmodified lines
238 await Effect.runPromise(persistence.rows.sources.insert({237 await Effect.runPromise(persistence.rows.sources.insert({
239 id: sourceId,238 id: sourceId,
240 workspaceId,239 workspaceId,
241 configKey: null,
242 recipeId,240 recipeId,
243 recipeRevisionId,241 recipeRevisionId,
244 name: "Cloudflare API",242 name: "Cloudflare API",
packages/control-plane/src/runtime/source-materialization.ts+6−12
@@ -47,7 +47,6 @@ export const syncSourceMaterialization = (input: { · 46 unmodified lines
47 if (47 if (
48 runtimeLocalWorkspace !== null48 runtimeLocalWorkspace !== null
49 && runtimeLocalWorkspace.installation.workspaceId === input.source.workspaceId49 && runtimeLocalWorkspace.installation.workspaceId === input.source.workspaceId
50 && input.source.configKey !== null
51 ) {50 ) {
52 if (!shouldIndexSource(input.source)) {51 if (!shouldIndexSource(input.source)) {
53 const state = yield* Effect.tryPromise({52 const state = yield* Effect.tryPromise({
@@ -55,13 +54,12 @@ export const syncSourceMaterialization = (input: { · 1 unmodified line
55 catch: (cause) =>54 catch: (cause) =>
56 cause instanceof Error ? cause : new Error(String(cause)),55 cause instanceof Error ? cause : new Error(String(cause)),
57 });56 });
58 const existingSourceState = state.sources[input.source.configKey];57 const existingSourceState = state.sources[input.source.id];
59 const nextState: LocalWorkspaceState = {58 const nextState: LocalWorkspaceState = {
60 ...state,59 ...state,
61 sources: {60 sources: {
62 ...state.sources,61 ...state.sources,
63 [input.source.configKey]: {62 [input.source.id]: {
64 id: input.source.id,
65 status: (input.source.enabled ? input.source.status : "draft") as SourceStatus,63 status: (input.source.enabled ? input.source.status : "draft") as SourceStatus,
66 lastError: null,64 lastError: null,
67 sourceHash: input.source.sourceHash,65 sourceHash: input.source.sourceHash,
@@ -100,10 +98,9 @@ export const syncSourceMaterialization = (input: { · 32 unmodified lines
100 try: () =>98 try: () =>
101 writeLocalSourceArtifact({99 writeLocalSourceArtifact({
102 context: runtimeLocalWorkspace.context,100 context: runtimeLocalWorkspace.context,
103 configKey: input.source.configKey!,101 sourceId: input.source.id,
104 artifact: buildLocalSourceArtifact({102 artifact: buildLocalSourceArtifact({
105 source: input.source,103 source: input.source,
106 configKey: input.source.configKey!,
107 materialization,104 materialization,
108 }),105 }),
109 }),106 }),
@@ -116,13 +113,12 @@ export const syncSourceMaterialization = (input: { · 6 unmodified lines
116 catch: (cause) =>113 catch: (cause) =>
117 cause instanceof Error ? cause : new Error(String(cause)),114 cause instanceof Error ? cause : new Error(String(cause)),
118 });115 });
119 const existingSourceState = state.sources[input.source.configKey];116 const existingSourceState = state.sources[input.source.id];
120 const nextState: LocalWorkspaceState = {117 const nextState: LocalWorkspaceState = {
121 ...state,118 ...state,
122 sources: {119 sources: {
123 ...state.sources,120 ...state.sources,
124 [input.source.configKey]: {121 [input.source.id]: {
125 id: input.source.id,
126 status: "connected",122 status: "connected",
127 lastError: null,123 lastError: null,
128 sourceHash: materialization.sourceHash,124 sourceHash: materialization.sourceHash,
@@ -179,7 +175,6 @@ export const persistMcpRecipeMaterializationFromManifest = (input: { · 50 unmodified lines
179 if (175 if (
180 runtimeLocalWorkspace !== null176 runtimeLocalWorkspace !== null
181 && runtimeLocalWorkspace.installation.workspaceId === input.source.workspaceId177 && runtimeLocalWorkspace.installation.workspaceId === input.source.workspaceId
182 && input.source.configKey !== null
183 ) {178 ) {
184 const materialization = materializationFromMcpManifestEntries({179 const materialization = materializationFromMcpManifestEntries({
185 recipeRevisionId: "src_recipe_rev_materialization" as never,180 recipeRevisionId: "src_recipe_rev_materialization" as never,
@@ -191,10 +186,9 @@ export const persistMcpRecipeMaterializationFromManifest = (input: { · 5 unmodified lines
191 try: () =>186 try: () =>
192 writeLocalSourceArtifact({187 writeLocalSourceArtifact({
193 context: runtimeLocalWorkspace.context,188 context: runtimeLocalWorkspace.context,
194 configKey: input.source.configKey!,189 sourceId: input.source.id,
195 artifact: buildLocalSourceArtifact({190 artifact: buildLocalSourceArtifact({
196 source: input.source,191 source: input.source,
197 configKey: input.source.configKey!,
198 materialization,192 materialization,
199 }),193 }),
200 }),194 }),
packages/control-plane/src/runtime/source-recipes-runtime.test.ts+0−5
@@ -87,7 +87,6 @@ const makeSource = (overrides: Partial<Source> = {}): Source => { · 86 unmodified lines
87 return {87 return {
88 id: SourceIdSchema.make("src_runtime_recipe"),88 id: SourceIdSchema.make("src_runtime_recipe"),
89 workspaceId: WorkspaceIdSchema.make("ws_runtime_recipe"),89 workspaceId: WorkspaceIdSchema.make("ws_runtime_recipe"),
90 configKey: null,
91 name: "GitHub",90 name: "GitHub",
92 kind,91 kind,
93 endpoint,92 endpoint,
@@ -373,7 +372,6 @@ describe("source-recipes-runtime", () => { · 279 unmodified lines
373 sourceRecord: {372 sourceRecord: {
374 id: SourceIdSchema.make("src_runtime_recipe"),373 id: SourceIdSchema.make("src_runtime_recipe"),
375 workspaceId: WorkspaceIdSchema.make("ws_runtime_recipe"),374 workspaceId: WorkspaceIdSchema.make("ws_runtime_recipe"),
376 configKey: null,
377 recipeId: SourceRecipeIdSchema.make("src_recipe_runtime"),375 recipeId: SourceRecipeIdSchema.make("src_recipe_runtime"),
378 recipeRevisionId: SourceRecipeRevisionIdSchema.make("src_recipe_rev_runtime"),376 recipeRevisionId: SourceRecipeRevisionIdSchema.make("src_recipe_rev_runtime"),
379 name: "GITHUB API",377 name: "GITHUB API",
@@ -545,7 +543,6 @@ describe("source-recipes-runtime", () => { · 165 unmodified lines
545 await Effect.runPromise(persistence.rows.sources.insert({543 await Effect.runPromise(persistence.rows.sources.insert({
546 id: SourceIdSchema.make(`src_shared_revision_${index}`),544 id: SourceIdSchema.make(`src_shared_revision_${index}`),
547 workspaceId,545 workspaceId,
548 configKey: null,
549 recipeId,546 recipeId,
550 recipeRevisionId,547 recipeRevisionId,
551 name,548 name,
@@ -624,7 +621,6 @@ describe("source-recipes-runtime", () => { · 72 unmodified lines
624 await Effect.runPromise(persistence.rows.sources.insert({621 await Effect.runPromise(persistence.rows.sources.insert({
625 id: sourceId,622 id: sourceId,
626 workspaceId,623 workspaceId,
627 configKey: null,
628 recipeId,624 recipeId,
629 recipeRevisionId,625 recipeRevisionId,
630 name: "GraphQL Demo",626 name: "GraphQL Demo",
@@ -692,7 +688,6 @@ describe("source-recipes-runtime", () => { · 61 unmodified lines
692 await Effect.runPromise(persistence.rows.sources.insert({688 await Effect.runPromise(persistence.rows.sources.insert({
693 id: sourceId,689 id: sourceId,
694 workspaceId,690 workspaceId,
695 configKey: null,
696 recipeId,691 recipeId,
697 recipeRevisionId,692 recipeRevisionId,
698 name: "Broken GitHub",693 name: "Broken GitHub",
packages/control-plane/src/runtime/source-recipes-runtime.ts+2−13
@@ -173,15 +173,11 @@ export const loadWorkspaceSourceRecipes = (input: { · 172 unmodified lines
173 173
174 const localRecipes = yield* Effect.forEach(sources, (source) =>174 const localRecipes = yield* Effect.forEach(sources, (source) =>
175 Effect.gen(function* () {175 Effect.gen(function* () {
176 if (source.configKey === null) {
177 return null;
178 }
179
180 const artifact = yield* Effect.tryPromise({176 const artifact = yield* Effect.tryPromise({
181 try: () =>177 try: () =>
182 readLocalSourceArtifact({178 readLocalSourceArtifact({
183 context: runtimeLocalWorkspace.context,179 context: runtimeLocalWorkspace.context,
184 configKey: source.configKey!,180 sourceId: source.id,
185 }),181 }),
186 catch: (cause) =>182 catch: (cause) =>
187 cause instanceof Error ? cause : new Error(String(cause)),183 cause instanceof Error ? cause : new Error(String(cause)),
@@ -193,7 +189,6 @@ export const loadWorkspaceSourceRecipes = (input: { · 5 unmodified lines
193 const sourceRecord: StoredSourceRecord = {189 const sourceRecord: StoredSourceRecord = {
194 id: source.id,190 id: source.id,
195 workspaceId: source.workspaceId,191 workspaceId: source.workspaceId,
196 configKey: source.configKey,
197 recipeId: artifact.recipeId,192 recipeId: artifact.recipeId,
198 recipeRevisionId: artifact.revision.id,193 recipeRevisionId: artifact.revision.id,
199 name: source.name,194 name: source.name,
@@ -313,17 +308,12 @@ export const loadSourceWithRecipe = (input: { · 113 unmodified lines
313 sourceId: input.sourceId,308 sourceId: input.sourceId,
314 actorAccountId: input.actorAccountId,309 actorAccountId: input.actorAccountId,
315 });310 });
316 if (source.configKey === null) {
317 return yield* Effect.fail(
318 new Error(`Source not found: workspaceId=${input.workspaceId} sourceId=${input.sourceId}`),
319 );
320 }
321 311
322 const artifact = yield* Effect.tryPromise({312 const artifact = yield* Effect.tryPromise({
323 try: () =>313 try: () =>
324 readLocalSourceArtifact({314 readLocalSourceArtifact({
325 context: runtimeLocalWorkspace.context,315 context: runtimeLocalWorkspace.context,
326 configKey: source.configKey!,316 sourceId: source.id,
327 }),317 }),
328 catch: (cause) =>318 catch: (cause) =>
329 cause instanceof Error ? cause : new Error(String(cause)),319 cause instanceof Error ? cause : new Error(String(cause)),
@@ -337,7 +327,6 @@ export const loadSourceWithRecipe = (input: { · 7 unmodified lines
337 const sourceRecord: StoredSourceRecord = {327 const sourceRecord: StoredSourceRecord = {
338 id: source.id,328 id: source.id,
339 workspaceId: source.workspaceId,329 workspaceId: source.workspaceId,
340 configKey: source.configKey,
341 recipeId: artifact.recipeId,330 recipeId: artifact.recipeId,
342 recipeRevisionId: artifact.revision.id,331 recipeRevisionId: artifact.revision.id,
343 name: source.name,332 name: source.name,
packages/control-plane/src/runtime/source-store.test.ts+0−1
@@ -52,7 +52,6 @@ const makeOpenApiSource = (input: { · 51 unmodified lines
52}): Source => ({52}): Source => ({
53 id: input.sourceId,53 id: input.sourceId,
54 workspaceId: input.workspaceId,54 workspaceId: input.workspaceId,
55 configKey: null,
56 name: input.name ?? "GitHub",55 name: input.name ?? "GitHub",
57 kind: "openapi",56 kind: "openapi",
58 endpoint: input.endpoint ?? "https://api.github.com",57 endpoint: input.endpoint ?? "https://api.github.com",
packages/control-plane/src/runtime/source-store.ts+56−110
@@ -1,5 +1,3 @@
1import { createHash } from "node:crypto";
2
3import type {1import type {
4 AccountId,2 AccountId,
5 AuthArtifact,3 AuthArtifact,
@@ -7,10 +5,12 @@ import type { · 1 unmodified line
7 LocalConfigSecretInput,5 LocalConfigSecretInput,
8 LocalConfigSource,6 LocalConfigSource,
9 Source,7 Source,
8 SourceId,
10 SourceRecipeId,9 SourceRecipeId,
11 SourceRecipeRevisionId,10 SourceRecipeRevisionId,
12 WorkspaceId,11 WorkspaceId,
13} from "#schema";12} from "#schema";
13import { SourceIdSchema } from "#schema";
14import { type SqlControlPlaneRows } from "#persistence";14import { type SqlControlPlaneRows } from "#persistence";
15import * as Effect from "effect/Effect";15import * as Effect from "effect/Effect";
16import * as Option from "effect/Option";16import * as Option from "effect/Option";
@@ -118,24 +118,22 @@ const trimOrNull = (value: string | null | undefined): string | null => { · 101 unmodified lines
118const cloneJson = <T>(value: T): T =>118const cloneJson = <T>(value: T): T =>
119 JSON.parse(JSON.stringify(value)) as T;119 JSON.parse(JSON.stringify(value)) as T;
120 120
121const localSourceIdForConfigKey = (input: {121const deriveLocalSourceId = (
122 workspaceRoot: string;122 source: Pick<Source, "namespace" | "name">,
123 configKey: string;123 used: ReadonlySet<string>,
124}): Source["id"] => {124): SourceId => {
125 const hash = createHash("sha256")
126 .update(`${input.workspaceRoot}:${input.configKey}`)
127 .digest("hex")
128 .slice(0, 16);
129 return `src_local_${hash}` as Source["id"];
130};
131
132const deriveLocalSourceConfigKey = (source: Pick<Source, "configKey" | "namespace" | "name">): string => {
133 const base =125 const base =
134 trimOrNull(source.configKey)126 trimOrNull(source.namespace)
135 ?? trimOrNull(source.namespace)
136 ?? trimOrNull(source.name)127 ?? trimOrNull(source.name)
137 ?? "source";128 ?? "source";
138 return slugify(base) || "source";129 const slugBase = slugify(base) || "source";
130 let candidate = slugBase;
131 let counter = 2;
132 while (used.has(candidate)) {
133 candidate = `${slugBase}-${counter}`;
134 counter += 1;
135 }
136 return SourceIdSchema.make(candidate);
139};137};
140 138
141const resolveLocalConfigSecretProviderAlias = (config: Awaited<ReturnType<typeof loadLocalExecutorConfig>>["config"]): string | null => {139const resolveLocalConfigSecretProviderAlias = (config: Awaited<ReturnType<typeof loadLocalExecutorConfig>>["config"]): string | null => {
@@ -257,34 +255,30 @@ const buildLocalSourceRecord = (input: { · 115 unmodified lines
257 workspaceId: WorkspaceId;255 workspaceId: WorkspaceId;
258 loadedConfig: Awaited<ReturnType<typeof loadLocalExecutorConfig>>;256 loadedConfig: Awaited<ReturnType<typeof loadLocalExecutorConfig>>;
259 workspaceState: LocalWorkspaceState;257 workspaceState: LocalWorkspaceState;
260 configKey: string;258 sourceId: SourceId;
261 actorAccountId?: AccountId | null;259 actorAccountId?: AccountId | null;
262 authArtifacts: ReadonlyArray<AuthArtifact>;260 authArtifacts: ReadonlyArray<AuthArtifact>;
263}): Effect.Effect<{261}): Effect.Effect<{
264 source: Source;262 source: Source;
265 configKey: string;263 sourceId: SourceId;
266}, Error, never> =>264}, Error, never> =>
267 Effect.gen(function* () {265 Effect.gen(function* () {
268 const sourceConfig = input.loadedConfig.config?.sources?.[input.configKey];266 const sourceConfig = input.loadedConfig.config?.sources?.[input.sourceId];
269 if (!sourceConfig) {267 if (!sourceConfig) {
270 return yield* Effect.fail(new Error(`Configured source not found for key ${input.configKey}`));268 return yield* Effect.fail(new Error(`Configured source not found for id ${input.sourceId}`));
271 }269 }
272 270
273 const existingState = input.workspaceState.sources[input.configKey];271 const existingState = input.workspaceState.sources[input.sourceId];
274 const adapter = getSourceAdapter(sourceConfig.kind);272 const adapter = getSourceAdapter(sourceConfig.kind);
275 const baseSource = yield* adapter.validateSource({273 const baseSource = yield* adapter.validateSource({
276 id: existingState?.id ?? localSourceIdForConfigKey({274 id: SourceIdSchema.make(input.sourceId),
277 workspaceRoot: input.context.workspaceRoot,
278 configKey: input.configKey,
279 }),
280 workspaceId: input.workspaceId,275 workspaceId: input.workspaceId,
281 configKey: input.configKey,276 name: trimOrNull(sourceConfig.name) ?? input.sourceId,
282 name: trimOrNull(sourceConfig.name) ?? input.configKey,
283 kind: sourceConfig.kind,277 kind: sourceConfig.kind,
284 endpoint: sourceConfig.connection.endpoint.trim(),278 endpoint: sourceConfig.connection.endpoint.trim(),
285 status: existingState?.status ?? ((sourceConfig.enabled ?? true) ? "connected" : "draft"),279 status: existingState?.status ?? ((sourceConfig.enabled ?? true) ? "connected" : "draft"),
286 enabled: sourceConfig.enabled ?? true,280 enabled: sourceConfig.enabled ?? true,
287 namespace: trimOrNull(sourceConfig.namespace) ?? input.configKey,281 namespace: trimOrNull(sourceConfig.namespace) ?? input.sourceId,
288 bindingVersion: adapter.bindingConfigVersion,282 bindingVersion: adapter.bindingConfigVersion,
289 binding: sourceConfig.binding,283 binding: sourceConfig.binding,
290 importAuthPolicy: adapter.defaultImportAuthPolicy,284 importAuthPolicy: adapter.defaultImportAuthPolicy,
@@ -303,7 +297,7 @@ const buildLocalSourceRecord = (input: { · 12 unmodified lines
303 const artifact = yield* Effect.tryPromise({297 const artifact = yield* Effect.tryPromise({
304 try: () => readLocalSourceArtifact({298 try: () => readLocalSourceArtifact({
305 context: input.context,299 context: input.context,
306 configKey: input.configKey,300 sourceId: input.sourceId,
307 }),301 }),
308 catch: (cause) =>302 catch: (cause) =>
309 cause instanceof Error ? cause : new Error(String(cause)),303 cause instanceof Error ? cause : new Error(String(cause)),
@@ -325,7 +319,6 @@ const buildLocalSourceRecord = (input: { · 15 unmodified lines
325 const sourceRecord = {319 const sourceRecord = {
326 id: baseSource.id,320 id: baseSource.id,
327 workspaceId: baseSource.workspaceId,321 workspaceId: baseSource.workspaceId,
328 configKey: input.configKey,
329 recipeId: artifact?.recipeId ?? stableSourceRecipeId(baseSource),322 recipeId: artifact?.recipeId ?? stableSourceRecipeId(baseSource),
330 recipeRevisionId: artifact?.revision.id ?? stableSourceRecipeRevisionId(baseSource),323 recipeRevisionId: artifact?.revision.id ?? stableSourceRecipeRevisionId(baseSource),
331 name: baseSource.name,324 name: baseSource.name,
@@ -350,39 +343,10 @@ const buildLocalSourceRecord = (input: { · 18 unmodified lines
350 343
351 return {344 return {
352 source,345 source,
353 configKey: input.configKey,346 sourceId: input.sourceId,
354 };347 };
355 });348 });
356 349
357const resolveLocalSourceConfigKey = (input: {
358 context: {
359 workspaceRoot: string;
360 };
361 loadedConfig: Awaited<ReturnType<typeof loadLocalExecutorConfig>>;
362 workspaceState: LocalWorkspaceState;
363 sourceId: Source["id"];
364}): string | null => {
365 const configKeys = Object.keys(input.loadedConfig.config?.sources ?? {});
366 for (const configKey of configKeys) {
367 const storedId = input.workspaceState.sources[configKey]?.id;
368 if (storedId === input.sourceId) {
369 return configKey;
370 }
371
372 if (
373 storedId === undefined
374 && localSourceIdForConfigKey({
375 workspaceRoot: input.context.workspaceRoot,
376 configKey,
377 }) === input.sourceId
378 ) {
379 return configKey;
380 }
381 }
382
383 return null;
384};
385
386export const loadSourcesInWorkspace = (350export const loadSourcesInWorkspace = (
387 rows: SqlControlPlaneRows,351 rows: SqlControlPlaneRows,
388 workspaceId: WorkspaceId,352 workspaceId: WorkspaceId,
@@ -396,14 +360,14 @@ export const loadSourcesInWorkspace = ( · 7 unmodified lines
396 const authArtifacts = yield* rows.authArtifacts.listByWorkspaceId(workspaceId);360 const authArtifacts = yield* rows.authArtifacts.listByWorkspaceId(workspaceId);
397 return yield* Effect.forEach(361 return yield* Effect.forEach(
398 Object.keys(localWorkspace.loadedConfig.config?.sources ?? {}),362 Object.keys(localWorkspace.loadedConfig.config?.sources ?? {}),
399 (configKey) =>363 (sourceId) =>
400 Effect.map(364 Effect.map(
401 buildLocalSourceRecord({365 buildLocalSourceRecord({
402 context: localWorkspace.context,366 context: localWorkspace.context,
403 workspaceId,367 workspaceId,
404 loadedConfig: localWorkspace.loadedConfig,368 loadedConfig: localWorkspace.loadedConfig,
405 workspaceState: localWorkspace.workspaceState,369 workspaceState: localWorkspace.workspaceState,
406 configKey,370 sourceId: SourceIdSchema.make(sourceId),
407 actorAccountId: options.actorAccountId,371 actorAccountId: options.actorAccountId,
408 authArtifacts,372 authArtifacts,
409 }),373 }),
@@ -446,14 +410,7 @@ export const loadSourceById = (rows: SqlControlPlaneRows, input: { · 36 unmodified lines
446 const localWorkspace = yield* resolveRuntimeLocalWorkspace(input.workspaceId);410 const localWorkspace = yield* resolveRuntimeLocalWorkspace(input.workspaceId);
447 if (localWorkspace !== null) {411 if (localWorkspace !== null) {
448 const authArtifacts = yield* rows.authArtifacts.listByWorkspaceId(input.workspaceId);412 const authArtifacts = yield* rows.authArtifacts.listByWorkspaceId(input.workspaceId);
449 const configKey = resolveLocalSourceConfigKey({413 if (!localWorkspace.loadedConfig.config?.sources?.[input.sourceId]) {
450 context: localWorkspace.context,
451 loadedConfig: localWorkspace.loadedConfig,
452 workspaceState: localWorkspace.workspaceState,
453 sourceId: input.sourceId,
454 });
455
456 if (configKey === null) {
457 return yield* Effect.fail(414 return yield* Effect.fail(
458 new Error(`Source not found: workspaceId=${input.workspaceId} sourceId=${input.sourceId}`),415 new Error(`Source not found: workspaceId=${input.workspaceId} sourceId=${input.sourceId}`),
459 );416 );
@@ -464,7 +421,7 @@ export const loadSourceById = (rows: SqlControlPlaneRows, input: { · 4 unmodified lines
464 workspaceId: input.workspaceId,421 workspaceId: input.workspaceId,
465 loadedConfig: localWorkspace.loadedConfig,422 loadedConfig: localWorkspace.loadedConfig,
466 workspaceState: localWorkspace.workspaceState,423 workspaceState: localWorkspace.workspaceState,
467 configKey,424 sourceId: input.sourceId,
468 actorAccountId: input.actorAccountId,425 actorAccountId: input.actorAccountId,
469 authArtifacts,426 authArtifacts,
470 });427 });
@@ -507,7 +464,6 @@ export const loadSourceById = (rows: SqlControlPlaneRows, input: { · 36 unmodified lines
507 464
508const configSourceFromLocalSource = (input: {465const configSourceFromLocalSource = (input: {
509 source: Source;466 source: Source;
510 configKey: string;
511 existingConfigAuth: LocalConfigSecretInput | undefined;467 existingConfigAuth: LocalConfigSecretInput | undefined;
512 config: Awaited<ReturnType<typeof loadLocalExecutorConfig>>["config"];468 config: Awaited<ReturnType<typeof loadLocalExecutorConfig>>["config"];
513}): LocalConfigSource => {469}): LocalConfigSource => {
@@ -518,10 +474,10 @@ const configSourceFromLocalSource = (input: { · 4 unmodified lines
518 });474 });
519 475
520 const common = {476 const common = {
521 ...(trimOrNull(input.source.name) !== trimOrNull(input.configKey)477 ...(trimOrNull(input.source.name) !== trimOrNull(input.source.id)
522 ? { name: input.source.name }478 ? { name: input.source.name }
523 : {}),479 : {}),
524 ...(trimOrNull(input.source.namespace) !== trimOrNull(input.configKey)480 ...(trimOrNull(input.source.namespace) !== trimOrNull(input.source.id)
525 ? { namespace: input.source.namespace ?? undefined }481 ? { namespace: input.source.namespace ?? undefined }
526 : {}),482 : {}),
527 ...(input.source.enabled === false ? { enabled: false } : {}),483 ...(input.source.enabled === false ? { enabled: false } : {}),
@@ -639,13 +595,7 @@ export const removeSourceById = (rows: SqlControlPlaneRows, input: { · 111 unmodified lines
639 Effect.gen(function* () {595 Effect.gen(function* () {
640 const localWorkspace = yield* resolveRuntimeLocalWorkspace(input.workspaceId);596 const localWorkspace = yield* resolveRuntimeLocalWorkspace(input.workspaceId);
641 if (localWorkspace !== null) {597 if (localWorkspace !== null) {
642 const configKey = resolveLocalSourceConfigKey({598 if (!localWorkspace.loadedConfig.config?.sources?.[input.sourceId]) {
643 context: localWorkspace.context,
644 loadedConfig: localWorkspace.loadedConfig,
645 workspaceState: localWorkspace.workspaceState,
646 sourceId: input.sourceId,
647 });
648 if (configKey === null) {
649 return false;599 return false;
650 }600 }
651 601
@@ -653,7 +603,7 @@ export const removeSourceById = (rows: SqlControlPlaneRows, input: { · 1 unmodified line
653 const sources = {603 const sources = {
654 ...(projectConfig.sources ?? {}),604 ...(projectConfig.sources ?? {}),
655 };605 };
656 delete sources[configKey];606 delete sources[input.sourceId];
657 yield* Effect.tryPromise({607 yield* Effect.tryPromise({
658 try: () =>608 try: () =>
659 writeProjectLocalExecutorConfig({609 writeProjectLocalExecutorConfig({
@@ -668,7 +618,7 @@ export const removeSourceById = (rows: SqlControlPlaneRows, input: { · 8 unmodified lines
668 });618 });
669 619
670 const {620 const {
671 [configKey]: _removedSource,621 [input.sourceId]: _removedSource,
672 ...remainingSources622 ...remainingSources
673 } = localWorkspace.workspaceState.sources;623 } = localWorkspace.workspaceState.sources;
674 const workspaceState: LocalWorkspaceState = {624 const workspaceState: LocalWorkspaceState = {
@@ -688,7 +638,7 @@ export const removeSourceById = (rows: SqlControlPlaneRows, input: { · 13 unmodified lines
688 try: () =>638 try: () =>
689 removeLocalSourceArtifact({639 removeLocalSourceArtifact({
690 context: localWorkspace.context,640 context: localWorkspace.context,
691 configKey,641 sourceId: input.sourceId,
692 }),642 }),
693 catch: (cause) =>643 catch: (cause) =>
694 cause instanceof Error ? cause : new Error(String(cause)),644 cause instanceof Error ? cause : new Error(String(cause)),
@@ -743,9 +693,20 @@ export const persistSource = ( · 48 unmodified lines
743 Effect.gen(function* () {693 Effect.gen(function* () {
744 const localWorkspace = yield* resolveRuntimeLocalWorkspace(source.workspaceId);694 const localWorkspace = yield* resolveRuntimeLocalWorkspace(source.workspaceId);
745 if (localWorkspace !== null) {695 if (localWorkspace !== null) {
696 const nextSource = {
697 ...source,
698 id:
699 localWorkspace.loadedConfig.config?.sources?.[source.id]
700 || localWorkspace.workspaceState.sources[source.id]
701 ? source.id
702 : deriveLocalSourceId(
703 source,
704 new Set(Object.keys(localWorkspace.loadedConfig.config?.sources ?? {})),
705 ),
706 } satisfies Source;
746 const existingAuthArtifacts = yield* rows.authArtifacts.listByWorkspaceAndSourceId({707 const existingAuthArtifacts = yield* rows.authArtifacts.listByWorkspaceAndSourceId({
747 workspaceId: source.workspaceId,708 workspaceId: nextSource.workspaceId,
748 sourceId: source.id,709 sourceId: nextSource.id,
749 });710 });
750 const existingRuntimeAuthArtifact = selectExactAuthArtifact({711 const existingRuntimeAuthArtifact = selectExactAuthArtifact({
751 authArtifacts: existingAuthArtifacts,712 authArtifacts: existingAuthArtifacts,
@@ -757,27 +718,13 @@ export const persistSource = ( · 5 unmodified lines
757 actorAccountId: options.actorAccountId,718 actorAccountId: options.actorAccountId,
758 slot: "import",719 slot: "import",
759 });720 });
760 const configKey =
761 source.configKey
762 ?? resolveLocalSourceConfigKey({
763 context: localWorkspace.context,
764 loadedConfig: localWorkspace.loadedConfig,
765 workspaceState: localWorkspace.workspaceState,
766 sourceId: source.id,
767 })
768 ?? deriveLocalSourceConfigKey(source);
769 const nextSource = {
770 ...source,
771 configKey,
772 } satisfies Source;
773 const projectConfig = cloneJson(localWorkspace.loadedConfig.projectConfig ?? {});721 const projectConfig = cloneJson(localWorkspace.loadedConfig.projectConfig ?? {});
774 const sources = {722 const sources = {
775 ...(projectConfig.sources ?? {}),723 ...(projectConfig.sources ?? {}),
776 };724 };
777 const existingConfigSource = sources[configKey];725 const existingConfigSource = sources[nextSource.id];
778 sources[configKey] = configSourceFromLocalSource({726 sources[nextSource.id] = configSourceFromLocalSource({
779 source: nextSource,727 source: nextSource,
780 configKey,
781 existingConfigAuth: existingConfigSource?.connection.auth,728 existingConfigAuth: existingConfigSource?.connection.auth,
782 config: localWorkspace.loadedConfig.config,729 config: localWorkspace.loadedConfig.config,
783 });730 });
@@ -810,8 +757,8 @@ export const persistSource = ( · 26 unmodified lines
810 });757 });
811 }758 }
812 yield* rows.authArtifacts.removeByWorkspaceSourceAndActor({759 yield* rows.authArtifacts.removeByWorkspaceSourceAndActor({
813 workspaceId: source.workspaceId,760 workspaceId: nextSource.workspaceId,
814 sourceId: source.id,761 sourceId: nextSource.id,
815 actorAccountId: options.actorAccountId ?? null,762 actorAccountId: options.actorAccountId ?? null,
816 slot: "runtime",763 slot: "runtime",
817 });764 });
@@ -839,8 +786,8 @@ export const persistSource = ( · 21 unmodified lines
839 });786 });
840 }787 }
841 yield* rows.authArtifacts.removeByWorkspaceSourceAndActor({788 yield* rows.authArtifacts.removeByWorkspaceSourceAndActor({
842 workspaceId: source.workspaceId,789 workspaceId: nextSource.workspaceId,
843 sourceId: source.id,790 sourceId: nextSource.id,
844 actorAccountId: options.actorAccountId ?? null,791 actorAccountId: options.actorAccountId ?? null,
845 slot: "import",792 slot: "import",
846 });793 });
@@ -861,13 +808,12 @@ export const persistSource = ( · 14 unmodified lines
861 next: importAuthArtifact,808 next: importAuthArtifact,
862 });809 });
863 810
864 const existingSourceState = localWorkspace.workspaceState.sources[configKey];811 const existingSourceState = localWorkspace.workspaceState.sources[nextSource.id];
865 const workspaceState: LocalWorkspaceState = {812 const workspaceState: LocalWorkspaceState = {
866 ...localWorkspace.workspaceState,813 ...localWorkspace.workspaceState,
867 sources: {814 sources: {
868 ...localWorkspace.workspaceState.sources,815 ...localWorkspace.workspaceState.sources,
869 [configKey]: {816 [nextSource.id]: {
870 id: nextSource.id,
871 status: nextSource.status,817 status: nextSource.status,
872 lastError: nextSource.lastError,818 lastError: nextSource.lastError,
873 sourceHash: nextSource.sourceHash,819 sourceHash: nextSource.sourceHash,
packages/control-plane/src/runtime/workspace-execution-environment.test.ts+0−2
@@ -589,7 +589,6 @@ const persistConnectedEchoTool = (input: { · 588 unmodified lines
589 const source: Source = {589 const source: Source = {
590 id: input.sourceId,590 id: input.sourceId,
591 workspaceId: input.workspaceId,591 workspaceId: input.workspaceId,
592 configKey: null,
593 name: "Counted MCP",592 name: "Counted MCP",
594 kind: "mcp",593 kind: "mcp",
595 endpoint: input.endpoint,594 endpoint: input.endpoint,
@@ -802,7 +801,6 @@ const persistConnectedGithubOpenApiSource = (input: { · 206 unmodified lines
802 const source: Source = {801 const source: Source = {
803 id: input.sourceId,802 id: input.sourceId,
804 workspaceId: input.workspaceId,803 workspaceId: input.workspaceId,
805 configKey: null,
806 name: "GitHub",804 name: "GitHub",
807 kind: "openapi",805 kind: "openapi",
808 endpoint: input.endpoint ?? "https://api.github.com",806 endpoint: input.endpoint ?? "https://api.github.com",
packages/control-plane/src/schema/models/source.ts+0−3
@@ -97,7 +97,6 @@ export const StoredSourceRecordSchema = Schema.transform( · 96 unmodified lines
97 Schema.Struct({97 Schema.Struct({
98 id: SourceIdSchema,98 id: SourceIdSchema,
99 workspaceId: WorkspaceIdSchema,99 workspaceId: WorkspaceIdSchema,
100 configKey: Schema.NullOr(Schema.String),
101 recipeId: SourceRecipeIdSchema,100 recipeId: SourceRecipeIdSchema,
102 recipeRevisionId: SourceRecipeRevisionIdSchema,101 recipeRevisionId: SourceRecipeRevisionIdSchema,
103 name: Schema.String,102 name: Schema.String,
@@ -118,7 +117,6 @@ export const StoredSourceRecordSchema = Schema.transform( · 14 unmodified lines
118 decode: (row, _input) => ({117 decode: (row, _input) => ({
119 id: row.sourceId,118 id: row.sourceId,
120 workspaceId: row.workspaceId,119 workspaceId: row.workspaceId,
121 configKey: null,
122 recipeId: row.recipeId,120 recipeId: row.recipeId,
123 recipeRevisionId: row.recipeRevisionId,121 recipeRevisionId: row.recipeRevisionId,
124 name: row.name,122 name: row.name,
@@ -158,7 +156,6 @@ export const StoredSourceRecordSchema = Schema.transform( · 33 unmodified lines
158export const SourceSchema = Schema.Struct({156export const SourceSchema = Schema.Struct({
159 id: SourceIdSchema,157 id: SourceIdSchema,
160 workspaceId: WorkspaceIdSchema,158 workspaceId: WorkspaceIdSchema,
161 configKey: Schema.NullOr(Schema.String),
162 name: Schema.String,159 name: Schema.String,
163 kind: SourceKindSchema,160 kind: SourceKindSchema,
164 endpoint: Schema.String,161 endpoint: Schema.String,
packages/kitchen-sink/src/source-runtime.test.ts+0−1
@@ -492,7 +492,6 @@ const openApiSource = (input: { · 491 unmodified lines
492}): Source => ({492}): Source => ({
493 id: input.sourceId,493 id: input.sourceId,
494 workspaceId: input.workspaceId,494 workspaceId: input.workspaceId,
495 configKey: null,
496 name: input.displayName,495 name: input.displayName,
497 kind: "openapi",496 kind: "openapi",
498 endpoint: input.baseUrl,497 endpoint: input.baseUrl,
packages/react/src/index.ts+0−1
@@ -661,7 +661,6 @@ const createOptimisticSource = (input: { · 660 unmodified lines
661 return {661 return {
662 id: `src_optimistic_${crypto.randomUUID()}` as Source["id"],662 id: `src_optimistic_${crypto.randomUUID()}` as Source["id"],
663 workspaceId: input.workspaceId,663 workspaceId: input.workspaceId,
664 configKey: null,
665 name: input.payload.name,664 name: input.payload.name,
666 kind: input.payload.kind,665 kind: input.payload.kind,
667 endpoint: input.payload.endpoint,666 endpoint: input.payload.endpoint,