History

Extract file-backed runtime out of platform sdk

Rhys Sullivan committed 7 months ago

Commit c06e00f1 parent 549c9aa72 files · +2211 −1677Browse files

Unified · Split

72 files
  1. apps/executor/src/server/server.test.ts +7 −5
  2. bun.lock +28 −0
  3. packages/platform/internal/src/tools/executor-admin.ts +28 −5
  4. packages/platform/sdk-file/package.json +33 −0
  5. packages/platform/sdk-file/src/capture-release-workspace-fixture.ts +2 −2
  6. packages/platform/sdk-file/src/config.test.ts +1 −1
  7. packages/platform/sdk-file/src/config.ts +31 −30
  8. packages/platform/sdk-file/src/control-plane-store.test.ts +0 −0
  9. packages/platform/sdk-file/src/control-plane-store.ts +1 −1
  10. packages/platform/sdk-file/src/errors.ts +0 −0
  11. packages/platform/sdk-file/src/index.ts +364 −0
  12. packages/platform/sdk-file/src/installation.test.ts +1 −1
  13. packages/platform/sdk-file/src/installation.ts +1 −1
  14. packages/platform/sdk-file/src/release-upgrade-fixtures.ts +1 −1
  15. packages/platform/sdk-file/src/secret-material-providers.ts +67 −64
  16. packages/platform/sdk-file/src/source-artifacts.test.ts +6 −2
  17. packages/platform/sdk-file/src/source-artifacts.ts +172 −0
  18. packages/platform/sdk-file/src/source-type-declarations.test.ts +6 −2
  19. packages/platform/sdk-file/src/source-type-declarations.ts +427 −0
  20. packages/platform/sdk-file/src/tools.ts +1 −41
  21. packages/platform/sdk-file/src/workspace-state.ts +14 −52
  22. packages/platform/sdk-file/tsconfig.json +13 −0
  23. packages/platform/sdk/package.json +0 −1
  24. packages/platform/sdk/src/backend.ts +4 −0
  25. packages/platform/sdk/src/local/operations.ts +9 −23
  26. packages/platform/sdk/src/local/secrets.ts +12 −72
  27. packages/platform/sdk/src/policies/operations.ts +32 −26
  28. packages/platform/sdk/src/runtime/__fixtures__/README.md +2 −2
  29. packages/platform/sdk/src/runtime/auth/auth-artifacts.ts +1 −1
  30. packages/platform/sdk/src/runtime/auth/auth-leases.ts +30 −26
  31. packages/platform/sdk/src/runtime/auth/mcp-auth-provider.ts +13 −15
  32. packages/platform/sdk/src/runtime/auth/provider-grant-lifecycle.ts +4 −5
  33. packages/platform/sdk/src/runtime/auth/source-auth-material.ts +18 −2
  34. packages/platform/sdk/src/runtime/catalog/source/reconcile.ts +2 −3
  35. packages/platform/sdk/src/runtime/catalog/source/runtime.ts +11 −20
  36. packages/platform/sdk/src/runtime/catalog/source/sync.ts +34 −42
  37. packages/platform/sdk/src/runtime/catalog/source/type-declarations.ts +14 −418
  38. packages/platform/sdk/src/runtime/control-plane-runtime.test.ts +9 −11
  39. packages/platform/sdk/src/runtime/execution/service.ts +1 −1
  40. packages/platform/sdk/src/runtime/execution/workspace/authorization.ts +13 −11
  41. packages/platform/sdk/src/runtime/execution/workspace/environment.ts +27 −9
  42. packages/platform/sdk/src/runtime/execution/workspace/local.ts +1 −1
  43. packages/platform/sdk/src/runtime/execution/workspace/source-catalog.ts +2 −2
  44. packages/platform/sdk/src/runtime/execution/workspace/tool-invoker.ts +21 −3
  45. packages/platform/sdk/src/runtime/index.ts +82 −32
  46. packages/platform/sdk/src/runtime/local-tool-runtime.ts +37 −0
  47. packages/platform/sdk/src/runtime/local/operations.ts +0 −1
  48. packages/platform/sdk/src/runtime/local/source-artifacts.ts +0 −377
  49. packages/platform/sdk/src/runtime/local/storage.ts +0 −225
  50. packages/platform/sdk/src/runtime/policy/policies-operations.ts +0 −1
  51. packages/platform/sdk/src/runtime/source-artifacts.ts +234 −0
  52. packages/platform/sdk/src/runtime/sources/executor-tools.ts +3 −3
  53. packages/platform/sdk/src/runtime/sources/source-auth-service.ts +25 −18
  54. packages/platform/sdk/src/runtime/sources/source-discovery.ts +0 −1
  55. packages/platform/sdk/src/runtime/sources/source-inspection.ts +0 −1
  56. packages/platform/sdk/src/runtime/sources/source-store.test.ts +2 −2
  57. packages/platform/sdk/src/runtime/sources/source-store.ts +45 −17
  58. packages/platform/sdk/src/runtime/sources/source-store/auth.ts +5 −5
  59. packages/platform/sdk/src/runtime/sources/source-store/config.ts +3 −3
  60. packages/platform/sdk/src/runtime/sources/source-store/deps.ts +20 −27
  61. packages/platform/sdk/src/runtime/sources/source-store/lifecycle.ts +11 −13
  62. packages/platform/sdk/src/runtime/sources/source-store/records.ts +7 −24
  63. packages/platform/sdk/src/runtime/workspace-config.ts +9 −0
  64. packages/platform/sdk/src/runtime/workspace-errors.ts +36 −0
  65. packages/platform/sdk/src/runtime/workspace-state.ts +42 −0
  66. packages/platform/sdk/src/runtime/workspace/config-secrets.ts +0 −0
  67. packages/platform/sdk/src/runtime/workspace/runtime-context.ts +4 −6
  68. packages/platform/sdk/src/runtime/workspace/secret-material-providers.ts +63 −0
  69. packages/platform/sdk/src/runtime/workspace/storage.ts +95 −0
  70. packages/platform/sdk/src/runtime/workspace/workspace-sync.ts +8 −18
  71. packages/platform/sdk/src/sources/inspection.ts +1 −1
  72. packages/platform/sdk/src/workspace.ts +15 −0
apps/executor/src/server/server.test.ts+7−5
@@ -22,14 +22,16 @@ import { · 21 unmodified lines
22 controlPlaneOpenApiSpec,22 controlPlaneOpenApiSpec,
23} from "@executor/platform-api";23} from "@executor/platform-api";
24import {24import {
25 buildLocalSourceArtifact,
26 catalogSyncResultFromMcpManifest,25 catalogSyncResultFromMcpManifest,
27 deriveLocalInstallation,26 type ResolveExecutionEnvironment,
27} from "@executor/platform-sdk/runtime";
28import {
29 buildLocalSourceArtifact,
30 getOrProvisionLocalInstallation,
28 resolveLocalWorkspaceContext,31 resolveLocalWorkspaceContext,
29 writeLocalSourceArtifact,32 writeLocalSourceArtifact,
30 writeProjectLocalExecutorConfig,33 writeProjectLocalExecutorConfig,
31 type ResolveExecutionEnvironment,34} from "@executor/platform-sdk-file";
32} from "@executor/platform-sdk/runtime";
33import { SourceIdSchema } from "@executor/platform-sdk/schema";35import { SourceIdSchema } from "@executor/platform-sdk/schema";
34import { makeSesExecutor } from "@executor/runtime-ses";36import { makeSesExecutor } from "@executor/runtime-ses";
35import { Client } from "@modelcontextprotocol/sdk/client/index.js";37import { Client } from "@modelcontextprotocol/sdk/client/index.js";
@@ -97,7 +99,7 @@ const writeConfiguredLocalMcpSource = (input: { · 61 unmodified lines
97 const context = yield* resolveLocalWorkspaceContext({99 const context = yield* resolveLocalWorkspaceContext({
98 workspaceRoot: input.workspaceRoot,100 workspaceRoot: input.workspaceRoot,
99 });101 });
100 const installation = deriveLocalInstallation(context);102 const installation = yield* getOrProvisionLocalInstallation({ context });
101 103
102 yield* writeProjectLocalExecutorConfig({104 yield* writeProjectLocalExecutorConfig({
103 context,105 context,
bun.lock+28−0
@@ -30,6 +30,7 @@ · 29 unmodified lines
30 "@executor/platform-api": "workspace:*",30 "@executor/platform-api": "workspace:*",
31 "@executor/platform-internal": "workspace:*",31 "@executor/platform-internal": "workspace:*",
32 "@executor/platform-sdk": "workspace:*",32 "@executor/platform-sdk": "workspace:*",
33 "@executor/platform-sdk-file": "workspace:*",
33 "@executor/server": "workspace:*",34 "@executor/server": "workspace:*",
34 "effect": "catalog:",35 "effect": "catalog:",
35 },36 },
@@ -136,6 +137,7 @@ · 100 unmodified lines
136 "@effect/platform": "catalog:",137 "@effect/platform": "catalog:",
137 "@effect/platform-node": "catalog:",138 "@effect/platform-node": "catalog:",
138 "@effect/vitest": "catalog:",139 "@effect/vitest": "catalog:",
140 "@executor/platform-sdk-file": "workspace:*",
139 "@types/jsdom": "^21.1.7",141 "@types/jsdom": "^21.1.7",
140 "@types/node": "catalog:",142 "@types/node": "catalog:",
141 "@types/react": "^19.2.10",143 "@types/react": "^19.2.10",
@@ -332,6 +334,29 @@ · 190 unmodified lines
332 "zod": "catalog:",334 "zod": "catalog:",
333 },335 },
334 },336 },
337 "packages/platform/sdk-file": {
338 "name": "@executor/platform-sdk-file",
339 "version": "0.0.0",
340 "dependencies": {
341 "@effect/platform": "catalog:",
342 "@effect/platform-node": "catalog:",
343 "@executor/codemode-core": "workspace:*",
344 "@executor/ir": "workspace:*",
345 "@executor/platform-sdk": "workspace:*",
346 "@executor/source-core": "workspace:*",
347 "@executor/source-graphql": "workspace:*",
348 "@executor/source-openapi": "workspace:*",
349 "effect": "catalog:",
350 "jsonc-parser": "3.3.1",
351 },
352 "devDependencies": {
353 "@effect/vitest": "catalog:",
354 "@types/node": "catalog:",
355 "bun-types": "catalog:",
356 "typescript": "^5.9.3",
357 "vitest": "catalog:",
358 },
359 },
335 "packages/platform/server": {360 "packages/platform/server": {
336 "name": "@executor/server",361 "name": "@executor/server",
337 "version": "0.0.0",362 "version": "0.0.0",
@@ -343,6 +368,7 @@ · 5 unmodified lines
343 "@executor/platform-api": "workspace:*",368 "@executor/platform-api": "workspace:*",
344 "@executor/platform-internal": "workspace:*",369 "@executor/platform-internal": "workspace:*",
345 "@executor/platform-sdk": "workspace:*",370 "@executor/platform-sdk": "workspace:*",
371 "@executor/platform-sdk-file": "workspace:*",
346 "@opentelemetry/api": "^1.9.0",372 "@opentelemetry/api": "^1.9.0",
347 "@opentelemetry/exporter-trace-otlp-grpc": "^0.213.0",373 "@opentelemetry/exporter-trace-otlp-grpc": "^0.213.0",
348 "@opentelemetry/resources": "^2.6.0",374 "@opentelemetry/resources": "^2.6.0",
@@ -684,6 +710,8 @@ · 335 unmodified lines
684 710
685 "@executor/platform-sdk": ["@executor/platform-sdk@workspace:packages/platform/sdk"],711 "@executor/platform-sdk": ["@executor/platform-sdk@workspace:packages/platform/sdk"],
686 712
713 "@executor/platform-sdk-file": ["@executor/platform-sdk-file@workspace:packages/platform/sdk-file"],
714
687 "@executor/react": ["@executor/react@workspace:packages/clients/react"],715 "@executor/react": ["@executor/react@workspace:packages/clients/react"],
688 716
689 "@executor/runtime-deno-subprocess": ["@executor/runtime-deno-subprocess@workspace:packages/kernel/runtime-deno-subprocess"],717 "@executor/runtime-deno-subprocess": ["@executor/runtime-deno-subprocess@workspace:packages/kernel/runtime-deno-subprocess"],
packages/platform/internal/src/tools/executor-admin.ts+28−5
@@ -20,13 +20,22 @@ import * as Schema from "effect/Schema"; · 19 unmodified lines
20 20
21import {21import {
22 ControlPlaneStore,22 ControlPlaneStore,
23 LocalInstanceConfigService,
24 SecretMaterialDeleterService,
25 SecretMaterialStorerService,
26 SecretMaterialUpdaterService,
23 type ControlPlaneStoreShape,27 type ControlPlaneStoreShape,
24 type WorkspaceInternalToolContext,28 type WorkspaceInternalToolContext,
25 RuntimeLocalWorkspaceState,
26 RuntimeSourceAuthService,29 RuntimeSourceAuthService,
27 RuntimeSourceCatalogSyncService,30 RuntimeSourceCatalogSyncService,
28 RuntimeSourceStore,31 RuntimeSourceStore,
29 RuntimeSourceStoreService,32 RuntimeSourceStoreService,
33} from "@executor/platform-sdk/runtime";
34import {
35 type RuntimeLocalWorkspaceState,
36 provideOptionalRuntimeLocalWorkspace,
37} from "../../../sdk/src/runtime/workspace/runtime-context";
38import {
30 SourceArtifactStore,39 SourceArtifactStore,
31 type SourceArtifactStoreShape,40 type SourceArtifactStoreShape,
32 WorkspaceConfigStore,41 WorkspaceConfigStore,
@@ -34,8 +43,7 @@ import { · 1 unmodified line
34 WorkspaceStateStore,43 WorkspaceStateStore,
35 type WorkspaceStateStoreShape,44 type WorkspaceStateStoreShape,
36 makeWorkspaceStorageLayer,45 makeWorkspaceStorageLayer,
37 provideOptionalRuntimeLocalWorkspace,46} from "../../../sdk/src/runtime/workspace/storage";
38} from "@executor/platform-sdk/runtime";
39import {47import {
40 CreateSecretPayloadSchema,48 CreateSecretPayloadSchema,
41 CreateSecretResultSchema,49 CreateSecretResultSchema,
@@ -53,7 +61,6 @@ import { · 11 unmodified lines
53 deleteLocalSecret,61 deleteLocalSecret,
54 discoverSource,62 discoverSource,
55 discoverSourceInspectionTools,63 discoverSourceInspectionTools,
56 getLocalInstanceConfig,
57 getPolicy,64 getPolicy,
58 getSource,65 getSource,
59 getSourceInspection,66 getSourceInspection,
@@ -201,6 +208,10 @@ const makeRuntimeLayer = (input: { · 141 unmodified lines
201 workspaceConfigStore: WorkspaceConfigStoreShape;208 workspaceConfigStore: WorkspaceConfigStoreShape;
202 workspaceStateStore: WorkspaceStateStoreShape;209 workspaceStateStore: WorkspaceStateStoreShape;
203 sourceArtifactStore: SourceArtifactStoreShape;210 sourceArtifactStore: SourceArtifactStoreShape;
211 instanceConfigResolver: WorkspaceInternalToolContext["instanceConfigResolver"];
212 storeSecretMaterial: WorkspaceInternalToolContext["storeSecretMaterial"];
213 deleteSecretMaterial: WorkspaceInternalToolContext["deleteSecretMaterial"];
214 updateSecretMaterial: WorkspaceInternalToolContext["updateSecretMaterial"];
204}) =>215}) =>
205 Layer.mergeAll(216 Layer.mergeAll(
206 Layer.succeed(ControlPlaneStore, input.controlPlaneStore),217 Layer.succeed(ControlPlaneStore, input.controlPlaneStore),
@@ -209,6 +220,10 @@ const makeRuntimeLayer = (input: { · 2 unmodified lines
209 RuntimeSourceCatalogSyncService,220 RuntimeSourceCatalogSyncService,
210 input.sourceCatalogSyncService,221 input.sourceCatalogSyncService,
211 ),222 ),
223 Layer.succeed(LocalInstanceConfigService, input.instanceConfigResolver),
224 Layer.succeed(SecretMaterialStorerService, input.storeSecretMaterial),
225 Layer.succeed(SecretMaterialDeleterService, input.deleteSecretMaterial),
226 Layer.succeed(SecretMaterialUpdaterService, input.updateSecretMaterial),
212 makeWorkspaceStorageLayer({227 makeWorkspaceStorageLayer({
213 workspaceConfigStore: input.workspaceConfigStore,228 workspaceConfigStore: input.workspaceConfigStore,
214 workspaceStateStore: input.workspaceStateStore,229 workspaceStateStore: input.workspaceStateStore,
@@ -220,8 +235,12 @@ const runRuntimeEffect = <A, E, R>(input: { · 5 unmodified lines
220 effect: Effect.Effect<A, E, R>;235 effect: Effect.Effect<A, E, R>;
221 runtimeLayer: Layer.Layer<236 runtimeLayer: Layer.Layer<
222 | ControlPlaneStore237 | ControlPlaneStore
238 | LocalInstanceConfigService
223 | RuntimeSourceStoreService239 | RuntimeSourceStoreService
224 | RuntimeSourceCatalogSyncService240 | RuntimeSourceCatalogSyncService
241 | SecretMaterialStorerService
242 | SecretMaterialDeleterService
243 | SecretMaterialUpdaterService
225 | WorkspaceConfigStore244 | WorkspaceConfigStore
226 | WorkspaceStateStore245 | WorkspaceStateStore
227 | SourceArtifactStore,246 | SourceArtifactStore,
@@ -263,6 +282,10 @@ export const createWorkspaceExecutorAdminToolMap = ( · 35 unmodified lines
263 workspaceConfigStore: input.workspaceConfigStore,282 workspaceConfigStore: input.workspaceConfigStore,
264 workspaceStateStore: input.workspaceStateStore,283 workspaceStateStore: input.workspaceStateStore,
265 sourceArtifactStore: input.sourceArtifactStore,284 sourceArtifactStore: input.sourceArtifactStore,
285 instanceConfigResolver: input.instanceConfigResolver,
286 storeSecretMaterial: input.storeSecretMaterial,
287 deleteSecretMaterial: input.deleteSecretMaterial,
288 updateSecretMaterial: input.updateSecretMaterial,
266 });289 });
267 const workspaceStorageLayer = makeWorkspaceStorageLayer({290 const workspaceStorageLayer = makeWorkspaceStorageLayer({
268 workspaceConfigStore: input.workspaceConfigStore,291 workspaceConfigStore: input.workspaceConfigStore,
@@ -295,7 +318,7 @@ export const createWorkspaceExecutorAdminToolMap = ( · 26 unmodified lines
295 "Get local instance config such as supported secret providers.",318 "Get local instance config such as supported secret providers.",
296 inputSchema: emptyInputSchema,319 inputSchema: emptyInputSchema,
297 outputSchema: instanceConfigOutputSchema,320 outputSchema: instanceConfigOutputSchema,
298 execute: () => Effect.runPromise(getLocalInstanceConfig()),321 execute: () => Effect.runPromise(input.instanceConfigResolver()),
299 },322 },
300 metadata,323 metadata,
301 }),324 }),
packages/platform/sdk-file/package.json+33−0
@@ -0,0 +1,33 @@
1{
2 "name": "@executor/platform-sdk-file",
3 "private": true,
4 "type": "module",
5 "exports": {
6 ".": "./src/index.ts"
7 },
8 "scripts": {
9 "fixture:release:capture": "bun run ./src/capture-release-workspace-fixture.ts",
10 "test": "vitest run",
11 "typecheck": "bunx tsc --noEmit -p tsconfig.json"
12 },
13 "dependencies": {
14 "@effect/platform": "catalog:",
15 "@effect/platform-node": "catalog:",
16 "@executor/codemode-core": "workspace:*",
17 "@executor/source-core": "workspace:*",
18 "@executor/source-graphql": "workspace:*",
19 "@executor/source-openapi": "workspace:*",
20 "@executor/ir": "workspace:*",
21 "@executor/platform-sdk": "workspace:*",
22 "effect": "catalog:",
23 "jsonc-parser": "3.3.1"
24 },
25 "devDependencies": {
26 "@effect/vitest": "catalog:",
27 "@types/node": "catalog:",
28 "bun-types": "catalog:",
29 "typescript": "^5.9.3",
30 "vitest": "catalog:"
31 },
32 "version": "0.0.0"
33}
packages/platform/sdk-file/src/capture-release-workspace-fixture.tsrenamed from packages/platform/sdk/src/runtime/local/capture-release-workspace-fixture.ts+2−2
@@ -6,7 +6,7 @@ import * as Data from "effect/Data"; · 5 unmodified lines
6import * as Effect from "effect/Effect";6import * as Effect from "effect/Effect";
7import * as Exit from "effect/Exit";7import * as Exit from "effect/Exit";
8 8
9import type { LocalExecutorConfig } from "#schema";9import type { LocalExecutorConfig } from "@executor/platform-sdk/schema";
10 10
11import {11import {
12 readOptionalLocalExecutorConfig,12 readOptionalLocalExecutorConfig,
@@ -37,7 +37,7 @@ type CaptureReleaseWorkspaceFixtureArgs = { · 24 unmodified lines
37};37};
38 38
39const usage = `Usage:39const usage = `Usage:
40 bun run ./src/runtime/local/capture-release-workspace-fixture.ts \\40 bun run ./src/capture-release-workspace-fixture.ts \\
41 --workspace-root /path/to/workspace \\41 --workspace-root /path/to/workspace \\
42 --source-id google-calendar \\42 --source-id google-calendar \\
43 --release-version v1.2.4-beta.1 \\43 --release-version v1.2.4-beta.1 \\
packages/platform/sdk-file/src/config.test.tsrenamed from packages/platform/sdk/src/runtime/local/config.test.ts+1−1
@@ -58,7 +58,7 @@ describe("local-config", () => { · 57 unmodified lines
58 "https://api.github.com",58 "https://api.github.com",
59 );59 );
60 expect(loaded.config?.runtime).toBe("ses");60 expect(loaded.config?.runtime).toBe("ses");
61 expect(context.homeStateDirectory).toContain("executor");61 expect(context.homeStateDirectory.toLowerCase()).toContain("executor");
62 }).pipe(Effect.provide(NodeFileSystem.layer)),62 }).pipe(Effect.provide(NodeFileSystem.layer)),
63 );63 );
64 64
packages/platform/sdk-file/src/config.tsrenamed from packages/platform/sdk/src/runtime/local/config.ts+31−30
@@ -13,9 +13,11 @@ import { · 12 unmodified lines
13 type LocalConfigPolicy,13 type LocalConfigPolicy,
14 type LocalConfigSecretProvider,14 type LocalConfigSecretProvider,
15 type LocalConfigSource,15 type LocalConfigSource,
16} from "#schema";16} from "@executor/platform-sdk/schema";
17import * as Effect from "effect/Effect";17import * as Effect from "effect/Effect";
18import * as Schema from "effect/Schema";18import * as Schema from "effect/Schema";
19import type { ExecutorWorkspaceDescriptor } from "@executor/platform-sdk";
20import type { LoadedLocalExecutorConfig } from "../../sdk/src/runtime/workspace-config";
19import {21import {
20 LocalExecutorConfigDecodeError,22 LocalExecutorConfigDecodeError,
21 LocalFileSystemError,23 LocalFileSystemError,
@@ -24,6 +26,33 @@ import { · 2 unmodified lines
24 26
25const decodeLocalExecutorConfig = Schema.decodeUnknownSync(LocalExecutorConfigSchema);27const decodeLocalExecutorConfig = Schema.decodeUnknownSync(LocalExecutorConfigSchema);
26 28
29export type FileLoadedExecutorConfig = LoadedLocalExecutorConfig & {
30 homeConfigPath: string;
31 projectConfigPath: string;
32};
33
34export const encodeLocalExecutorConfig = (config: LocalExecutorConfig): string =>
35 `${JSON.stringify(config, null, 2)}\n`;
36
37export const resolveConfigRelativePath = (input: {
38 path: string;
39 workspaceRoot: string;
40}): string => {
41 const trimmed = input.path.trim();
42 if (trimmed.startsWith("~/")) {
43 return join(homedir(), trimmed.slice(2));
44 }
45 if (trimmed === "~") {
46 return homedir();
47 }
48 if (isAbsolute(trimmed)) {
49 return trimmed;
50 }
51 return resolve(input.workspaceRoot, trimmed);
52};
53
54export type { LoadedLocalExecutorConfig } from "../../sdk/src/runtime/workspace-config";
55
27const PROJECT_CONFIG_BASENAME = "executor.jsonc";56const PROJECT_CONFIG_BASENAME = "executor.jsonc";
28const PROJECT_CONFIG_DIRECTORY = ".executor";57const PROJECT_CONFIG_DIRECTORY = ".executor";
29const EXECUTOR_CONFIG_DIR_ENV = "EXECUTOR_CONFIG_DIR";58const EXECUTOR_CONFIG_DIR_ENV = "EXECUTOR_CONFIG_DIR";
@@ -329,14 +358,6 @@ export type ResolvedLocalWorkspaceContext = { · 299 unmodified lines
329 stateDirectory: string;358 stateDirectory: string;
330};359};
331 360
332export type LoadedLocalExecutorConfig = {
333 config: LocalExecutorConfig | null;
334 homeConfig: LocalExecutorConfig | null;
335 projectConfig: LocalExecutorConfig | null;
336 homeConfigPath: string;
337 projectConfigPath: string;
338};
339
340export const resolveLocalWorkspaceContext = (input: {361export const resolveLocalWorkspaceContext = (input: {
341 cwd?: string;362 cwd?: string;
342 workspaceRoot?: string;363 workspaceRoot?: string;
@@ -409,7 +430,7 @@ export const readOptionalLocalExecutorConfig = ( · 66 unmodified lines
409export const loadLocalExecutorConfig = (430export const loadLocalExecutorConfig = (
410 context: ResolvedLocalWorkspaceContext,431 context: ResolvedLocalWorkspaceContext,
411): Effect.Effect<432): Effect.Effect<
412 LoadedLocalExecutorConfig,433 FileLoadedExecutorConfig,
413 LocalFileSystemError | LocalExecutorConfigDecodeError,434 LocalFileSystemError | LocalExecutorConfigDecodeError,
414 FileSystem.FileSystem435 FileSystem.FileSystem
415> =>436> =>
@@ -428,9 +449,6 @@ export const loadLocalExecutorConfig = ( · 12 unmodified lines
428 };449 };
429 });450 });
430 451
431export const encodeLocalExecutorConfig = (config: LocalExecutorConfig): string =>
432 `${JSON.stringify(config, null, 2)}\n`;
433
434export const writeProjectLocalExecutorConfig = (input: {452export const writeProjectLocalExecutorConfig = (input: {
435 context: ResolvedLocalWorkspaceContext;453 context: ResolvedLocalWorkspaceContext;
436 config: LocalExecutorConfig;454 config: LocalExecutorConfig;
@@ -448,22 +466,5 @@ export const writeProjectLocalExecutorConfig = (input: { · 11 unmodified lines
448 );466 );
449 });467 });
450 468
451export const resolveConfigRelativePath = (input: {
452 path: string;
453 workspaceRoot: string;
454}): string => {
455 const trimmed = input.path.trim();
456 if (trimmed.startsWith("~/")) {
457 return join(homedir(), trimmed.slice(2));
458 }
459 if (trimmed === "~") {
460 return homedir();
461 }
462 if (isAbsolute(trimmed)) {
463 return trimmed;
464 }
465 return resolve(input.workspaceRoot, trimmed);
466};
467
468export const defaultWorkspaceDisplayName = (context: ResolvedLocalWorkspaceContext): string =>469export const defaultWorkspaceDisplayName = (context: ResolvedLocalWorkspaceContext): string =>
469 trimOrUndefined(context.workspaceName) ?? "workspace";470 trimOrUndefined(context.workspaceName) ?? "workspace";
packages/platform/sdk-file/src/control-plane-store.test.tsrenamed from packages/platform/sdk/src/runtime/local/control-plane-store.test.ts+0−0

File renamed without changes.

packages/platform/sdk-file/src/control-plane-store.tsrenamed from packages/platform/sdk/src/runtime/local/control-plane-store.ts+1−1
@@ -24,7 +24,7 @@ import { · 23 unmodified lines
24 WorkspaceOauthClientSchema,24 WorkspaceOauthClientSchema,
25 type WorkspaceSourceOauthClient,25 type WorkspaceSourceOauthClient,
26 WorkspaceSourceOauthClientSchema,26 WorkspaceSourceOauthClientSchema,
27} from "#schema";27} from "@executor/platform-sdk/schema";
28import * as Effect from "effect/Effect";28import * as Effect from "effect/Effect";
29import * as Option from "effect/Option";29import * as Option from "effect/Option";
30import * as Schema from "effect/Schema";30import * as Schema from "effect/Schema";
packages/platform/sdk-file/src/errors.tsrenamed from packages/platform/sdk/src/runtime/local/errors.ts+0−0

File renamed without changes.

packages/platform/sdk-file/src/index.ts+364−0
@@ -0,0 +1,364 @@
1import { FileSystem } from "@effect/platform";
2import { NodeFileSystem } from "@effect/platform-node";
3import {
4 createExecutor,
5 createExecutorEffect,
6 type CreateExecutorOptions,
7 type Executor,
8 type ExecutorBackend,
9 type ExecutorBackendServices,
10 type ExecutorRuntimeOptions,
11 type ExecutorWorkspaceDescriptor,
12} from "@executor/platform-sdk";
13import type {
14 LocalExecutorConfig,
15 LocalInstallation,
16} from "@executor/platform-sdk/schema";
17import {
18 type RuntimeControlPlaneOptions,
19 type ControlPlaneRuntime,
20 type ResolveSecretMaterial,
21 createControlPlaneRuntimeFromServices,
22} from "@executor/platform-sdk/runtime";
23import * as Effect from "effect/Effect";
24import type { LocalToolRuntime } from "../../sdk/src/runtime/local-tool-runtime";
25import {
26 type LoadedLocalExecutorConfig,
27 type ResolvedLocalWorkspaceContext,
28 loadLocalExecutorConfig,
29 resolveConfigRelativePath,
30 resolveLocalWorkspaceContext,
31 writeProjectLocalExecutorConfig,
32} from "./config";
33import { createLocalControlPlanePersistence } from "./control-plane-store";
34import {
35 createDefaultSecretMaterialDeleter,
36 createLocalInstanceConfigResolver,
37 createDefaultSecretMaterialResolver,
38 createDefaultSecretMaterialStorer,
39 createDefaultSecretMaterialUpdater,
40} from "./secret-material-providers";
41import {
42 buildLocalSourceArtifact,
43 readLocalSourceArtifact,
44 removeLocalSourceArtifact,
45 writeLocalSourceArtifact,
46} from "./source-artifacts";
47import {
48 getOrProvisionLocalInstallation,
49 loadLocalInstallation,
50} from "./installation";
51import {
52 loadLocalToolRuntime,
53} from "./tools";
54import {
55 loadLocalWorkspaceState,
56 writeLocalWorkspaceState,
57} from "./workspace-state";
58import {
59 refreshSourceTypeDeclarationInBackground,
60 refreshWorkspaceSourceTypeDeclarationsInBackground,
61} from "./source-type-declarations";
62
63const toError = (cause: unknown): Error =>
64 cause instanceof Error ? cause : new Error(String(cause));
65
66const bindFileSystem = <A, E>(
67 fileSystem: FileSystem.FileSystem,
68 effect: Effect.Effect<A, E, FileSystem.FileSystem>,
69): Effect.Effect<A, E, never> =>
70 effect.pipe(Effect.provideService(FileSystem.FileSystem, fileSystem));
71
72export type CreateLocalExecutorBackendOptions = {
73 cwd?: string;
74 workspaceRoot?: string;
75 homeConfigPath?: string;
76 homeStateDirectory?: string;
77 localDataDir?: string;
78};
79
80type CreateLocalRuntimeOptions = CreateLocalExecutorBackendOptions &
81 RuntimeControlPlaneOptions;
82
83type LocalWorkspaceState = Parameters<
84 typeof writeLocalWorkspaceState
85>[0]["state"];
86
87type LocalSourceArtifact = Parameters<
88 typeof writeLocalSourceArtifact
89>[0]["artifact"];
90
91const createBoundInstallationStore = (
92 context: ResolvedLocalWorkspaceContext,
93): {
94 load: () => Effect.Effect<LocalInstallation, Error, never>;
95 getOrProvision: () => Effect.Effect<LocalInstallation, Error, never>;
96} => ({
97 load: () => loadLocalInstallation(context).pipe(Effect.mapError(toError)),
98 getOrProvision: () =>
99 getOrProvisionLocalInstallation({ context }).pipe(
100 Effect.mapError(toError),
101 ),
102});
103
104const createBoundWorkspaceConfigStore = (
105 context: ResolvedLocalWorkspaceContext,
106 fileSystem: FileSystem.FileSystem,
107): {
108 load: () => Effect.Effect<LoadedLocalExecutorConfig, Error, never>;
109 writeProject: (config: LocalExecutorConfig) => Effect.Effect<void, Error, never>;
110 resolveRelativePath: typeof resolveConfigRelativePath;
111} => ({
112 load: () =>
113 bindFileSystem(fileSystem, loadLocalExecutorConfig(context)).pipe(
114 Effect.mapError(toError),
115 ),
116 writeProject: (config) =>
117 bindFileSystem(
118 fileSystem,
119 writeProjectLocalExecutorConfig({ context, config }),
120 ).pipe(Effect.mapError(toError)),
121 resolveRelativePath: resolveConfigRelativePath,
122});
123
124const createBoundWorkspaceStateStore = (
125 context: ResolvedLocalWorkspaceContext,
126 fileSystem: FileSystem.FileSystem,
127): {
128 load: () => Effect.Effect<LocalWorkspaceState, Error, never>;
129 write: (state: LocalWorkspaceState) => Effect.Effect<void, Error, never>;
130} => ({
131 load: () =>
132 bindFileSystem(fileSystem, loadLocalWorkspaceState(context)).pipe(
133 Effect.mapError(toError),
134 ),
135 write: (state) =>
136 bindFileSystem(
137 fileSystem,
138 writeLocalWorkspaceState({ context, state }),
139 ).pipe(Effect.mapError(toError)),
140});
141
142const createBoundSourceArtifactStore = (
143 context: ResolvedLocalWorkspaceContext,
144 fileSystem: FileSystem.FileSystem,
145): {
146 build: typeof buildLocalSourceArtifact;
147 read: (sourceId: string) => Effect.Effect<LocalSourceArtifact | null, Error, never>;
148 write: (input: {
149 sourceId: string;
150 artifact: LocalSourceArtifact;
151 }) => Effect.Effect<void, Error, never>;
152 remove: (sourceId: string) => Effect.Effect<void, Error, never>;
153} => ({
154 build: buildLocalSourceArtifact,
155 read: (sourceId) =>
156 bindFileSystem(
157 fileSystem,
158 readLocalSourceArtifact({ context, sourceId }),
159 ).pipe(Effect.mapError(toError)),
160 write: ({ sourceId, artifact }) =>
161 bindFileSystem(
162 fileSystem,
163 writeLocalSourceArtifact({ context, sourceId, artifact }),
164 ).pipe(Effect.mapError(toError)),
165 remove: (sourceId) =>
166 bindFileSystem(
167 fileSystem,
168 removeLocalSourceArtifact({ context, sourceId }),
169 ).pipe(Effect.mapError(toError)),
170});
171
172const createBoundLocalToolRuntimeLoader = (
173 context: ResolvedLocalWorkspaceContext,
174 fileSystem: FileSystem.FileSystem,
175): ExecutorBackendServices["localToolRuntimeLoader"] => ({
176 load: () =>
177 bindFileSystem(fileSystem, loadLocalToolRuntime(context)),
178});
179
180const createBoundSourceTypeDeclarationsRefresher = (
181 context: ResolvedLocalWorkspaceContext,
182): NonNullable<ExecutorBackendServices["sourceTypeDeclarationsRefresher"]> => ({
183 refreshWorkspaceInBackground: ({ entries }) =>
184 Effect.sync(() => {
185 refreshWorkspaceSourceTypeDeclarationsInBackground({
186 context,
187 entries,
188 });
189 }),
190 refreshSourceInBackground: ({ source, snapshot }) =>
191 Effect.sync(() => {
192 refreshSourceTypeDeclarationInBackground({
193 context,
194 source,
195 snapshot,
196 });
197 }),
198});
199
200const toExecutorWorkspaceContext = (
201 context: ResolvedLocalWorkspaceContext,
202): ExecutorWorkspaceDescriptor => ({
203 workspaceName: context.workspaceName,
204 workspaceRoot: context.workspaceRoot,
205 metadata: {
206 kind: "file",
207 configDirectory: context.configDirectory,
208 projectConfigPath: context.projectConfigPath,
209 homeConfigPath: context.homeConfigPath,
210 homeStateDirectory: context.homeStateDirectory,
211 artifactsDirectory: context.artifactsDirectory,
212 stateDirectory: context.stateDirectory,
213 },
214});
215
216export const createLocalExecutorServicesEffect = (
217 options: CreateLocalExecutorBackendOptions = {},
218 runtimeOptions: RuntimeControlPlaneOptions = {},
219): Effect.Effect<ExecutorBackendServices, Error> =>
220 Effect.gen(function* () {
221 const fileSystem = yield* FileSystem.FileSystem;
222 const workspaceContext = yield* bindFileSystem(
223 fileSystem,
224 resolveLocalWorkspaceContext({
225 cwd: options.cwd,
226 workspaceRoot: options.workspaceRoot,
227 homeConfigPath: options.homeConfigPath,
228 homeStateDirectory: options.homeStateDirectory,
229 }),
230 ).pipe(Effect.mapError(toError));
231
232 const persistence = createLocalControlPlanePersistence(
233 workspaceContext,
234 fileSystem,
235 );
236 const workspaceConfigStore = createBoundWorkspaceConfigStore(
237 workspaceContext,
238 fileSystem,
239 );
240 const loadedConfig = yield* workspaceConfigStore.load();
241 const resolveSecretMaterial: ResolveSecretMaterial =
242 runtimeOptions.resolveSecretMaterial
243 ?? createDefaultSecretMaterialResolver({
244 rows: persistence.rows,
245 localConfig: loadedConfig.config,
246 workspaceRoot: workspaceContext.workspaceRoot,
247 });
248
249 return {
250 workspace: toExecutorWorkspaceContext(workspaceContext),
251 installationStore: createBoundInstallationStore(workspaceContext),
252 workspaceConfigStore,
253 workspaceStateStore: createBoundWorkspaceStateStore(
254 workspaceContext,
255 fileSystem,
256 ),
257 sourceArtifactStore: createBoundSourceArtifactStore(
258 workspaceContext,
259 fileSystem,
260 ),
261 localToolRuntimeLoader: createBoundLocalToolRuntimeLoader(
262 workspaceContext,
263 fileSystem,
264 ),
265 sourceTypeDeclarationsRefresher:
266 createBoundSourceTypeDeclarationsRefresher(workspaceContext),
267 secretMaterial: {
268 resolve: resolveSecretMaterial,
269 store: createDefaultSecretMaterialStorer({
270 rows: persistence.rows,
271 }),
272 delete: createDefaultSecretMaterialDeleter({
273 rows: persistence.rows,
274 }),
275 update: createDefaultSecretMaterialUpdater({
276 rows: persistence.rows,
277 }),
278 },
279 instanceConfig: {
280 resolve: createLocalInstanceConfigResolver(),
281 },
282 persistence,
283 } satisfies ExecutorBackendServices;
284 }).pipe(Effect.provide(NodeFileSystem.layer));
285
286export const createLocalExecutorBackend = (
287 options: CreateLocalExecutorBackendOptions = {},
288): ExecutorBackend => ({
289 createRuntime: (runtimeOptions) =>
290 Effect.flatMap(
291 createLocalExecutorServicesEffect(options, runtimeOptions),
292 (services) =>
293 createControlPlaneRuntimeFromServices({
294 ...runtimeOptions,
295 services,
296 }),
297 ).pipe(Effect.provide(NodeFileSystem.layer)),
298});
299
300export const createLocalControlPlaneRuntime = (
301 options: CreateLocalRuntimeOptions = {},
302): Effect.Effect<ControlPlaneRuntime, Error> =>
303 Effect.flatMap(
304 createLocalExecutorServicesEffect(options, options),
305 (services) =>
306 createControlPlaneRuntimeFromServices({
307 executionResolver: options.executionResolver,
308 createInternalToolMap: options.createInternalToolMap,
309 resolveSecretMaterial: options.resolveSecretMaterial,
310 getLocalServerBaseUrl: options.getLocalServerBaseUrl,
311 services,
312 }),
313 ).pipe(Effect.provide(NodeFileSystem.layer));
314
315export const createLocalExecutorEffect = (
316 options: CreateLocalExecutorBackendOptions & ExecutorRuntimeOptions = {},
317): Effect.Effect<Executor, Error> =>
318 createExecutorEffect({
319 backend: createLocalExecutorBackend(options),
320 executionResolver: options.executionResolver,
321 createInternalToolMap: options.createInternalToolMap,
322 resolveSecretMaterial: options.resolveSecretMaterial,
323 getLocalServerBaseUrl: options.getLocalServerBaseUrl,
324 } satisfies CreateExecutorOptions);
325
326export const createLocalExecutor = (
327 options: CreateLocalExecutorBackendOptions & ExecutorRuntimeOptions = {},
328): Promise<Executor> => Effect.runPromise(createLocalExecutorEffect(options));
329
330export {
331 deriveLocalInstallation,
332 getOrProvisionLocalInstallation,
333 loadLocalInstallation,
334} from "./installation";
335export {
336 loadLocalControlPlaneState,
337 writeLocalControlPlaneState,
338} from "./control-plane-store";
339export {
340 loadLocalExecutorConfig,
341 resolveConfigRelativePath,
342 resolveLocalWorkspaceContext,
343 writeProjectLocalExecutorConfig,
344} from "./config";
345export {
346 loadLocalWorkspaceState,
347 writeLocalWorkspaceState,
348} from "./workspace-state";
349export {
350 buildLocalSourceArtifact,
351 readLocalSourceArtifact,
352 removeLocalSourceArtifact,
353 writeLocalSourceArtifact,
354} from "./source-artifacts";
355export {
356 refreshSourceTypeDeclarationInBackground,
357 refreshWorkspaceSourceTypeDeclarationsInBackground,
358 syncSourceTypeDeclarationNode,
359 syncWorkspaceSourceTypeDeclarationsNode,
360} from "./source-type-declarations";
361export type {
362 LoadedLocalExecutorConfig,
363 ResolvedLocalWorkspaceContext,
364} from "./config";
packages/platform/sdk-file/src/installation.test.tsrenamed from packages/platform/sdk/src/runtime/local/installation.test.ts+1−1
@@ -5,7 +5,7 @@ import { NodeFileSystem } from "@effect/platform-node"; · 4 unmodified lines
5import { describe, expect, it } from "@effect/vitest";5import { describe, expect, it } from "@effect/vitest";
6import * as Effect from "effect/Effect";6import * as Effect from "effect/Effect";
7 7
8import { createLocalControlPlaneRuntime as createControlPlaneRuntime } from "../../../../sdk-file/src/index";8import { createLocalControlPlaneRuntime as createControlPlaneRuntime } from "./index";
9import { getOrProvisionLocalInstallation } from "./installation";9import { getOrProvisionLocalInstallation } from "./installation";
10import { resolveLocalWorkspaceContext } from "./config";10import { resolveLocalWorkspaceContext } from "./config";
11 11
packages/platform/sdk-file/src/installation.tsrenamed from packages/platform/sdk/src/runtime/local/installation.ts+1−1
@@ -4,7 +4,7 @@ import { · 3 unmodified lines
4 AccountIdSchema,4 AccountIdSchema,
5 WorkspaceIdSchema,5 WorkspaceIdSchema,
6 type LocalInstallation,6 type LocalInstallation,
7} from "#schema";7} from "@executor/platform-sdk/schema";
8import * as Effect from "effect/Effect";8import * as Effect from "effect/Effect";
9 9
10import type { ResolvedLocalWorkspaceContext } from "./config";10import type { ResolvedLocalWorkspaceContext } from "./config";
packages/platform/sdk-file/src/release-upgrade-fixtures.tsrenamed from packages/platform/sdk/src/runtime/local/release-upgrade-fixtures.ts+1−1
@@ -28,7 +28,7 @@ export type ReleaseWorkspaceFixture = ReleaseWorkspaceFixtureManifest & { · 27 unmodified lines
28};28};
29 29
30export const releaseWorkspaceFixturesRoot = fileURLToPath(30export const releaseWorkspaceFixturesRoot = fileURLToPath(
31 new URL("../__fixtures__", import.meta.url),31 new URL("../../sdk/src/runtime/__fixtures__", import.meta.url),
32);32);
33 33
34const decodeReleaseWorkspaceFixtureManifest = Schema.decodeUnknownSync(34const decodeReleaseWorkspaceFixtureManifest = Schema.decodeUnknownSync(
packages/platform/sdk-file/src/secret-material-providers.tsrenamed from packages/platform/sdk/src/runtime/local/secret-material-providers.ts+67−64
@@ -7,22 +7,34 @@ import { NodeFileSystem } from "@effect/platform-node"; · 6 unmodified lines
7import {7import {
8 type LocalConfigSecretProvider,8 type LocalConfigSecretProvider,
9 type LocalExecutorConfig,9 type LocalExecutorConfig,
10 type SecretMaterial,
11 type SecretMaterialPurpose,10 type SecretMaterialPurpose,
12 SecretMaterialIdSchema,
13 type SecretRef,11 type SecretRef,
14} from "#schema";12 type SecretMaterial,
15import * as Context from "effect/Context";13 SecretMaterialIdSchema,
14} from "@executor/platform-sdk/schema";
16import * as Effect from "effect/Effect";15import * as Effect from "effect/Effect";
17import * as Layer from "effect/Layer";16import * as Layer from "effect/Layer";
18import * as Option from "effect/Option";17import * as Option from "effect/Option";
19 18
19import {
20 LocalInstanceConfigService,
21 SecretMaterialDeleterService,
22 SecretMaterialResolverService,
23 SecretMaterialStorerService,
24 SecretMaterialUpdaterService,
25 type ResolveSecretMaterial,
26 type ResolveInstanceConfig,
27 type SecretMaterialResolveContext,
28 type StoreSecretMaterial,
29 type DeleteSecretMaterial,
30 type UpdateSecretMaterial,
31} from "../../sdk/src/runtime/workspace/secret-material-providers";
32import { fromConfigSecretProviderId } from "../../sdk/src/runtime/workspace/config-secrets";
33import { getRuntimeLocalWorkspaceOption } from "../../sdk/src/runtime/workspace/runtime-context";
34import { ControlPlaneStore } from "../../sdk/src/runtime/store";
35import type { ControlPlaneStoreShape } from "../../sdk/src/runtime/store";
36import { runtimeEffectError } from "../../sdk/src/runtime/effect-errors";
20import { resolveConfigRelativePath } from "./config";37import { resolveConfigRelativePath } from "./config";
21import { fromConfigSecretProviderId } from "./config-secrets";
22import { getRuntimeLocalWorkspaceOption } from "./runtime-context";
23import { ControlPlaneStore } from "../store";
24import type { ControlPlaneStoreShape } from "../store";
25import { runtimeEffectError } from "../effect-errors";
26 38
27export const ENV_SECRET_PROVIDER_ID = "env";39export const ENV_SECRET_PROVIDER_ID = "env";
28export const PARAMS_SECRET_PROVIDER_ID = "params";40export const PARAMS_SECRET_PROVIDER_ID = "params";
@@ -36,54 +48,6 @@ export type SecretStoreProviderId = · 7 unmodified lines
36 | typeof KEYCHAIN_SECRET_PROVIDER_ID48 | typeof KEYCHAIN_SECRET_PROVIDER_ID
37 | typeof LOCAL_SECRET_PROVIDER_ID;49 | typeof LOCAL_SECRET_PROVIDER_ID;
38 50
39export type SecretMaterialResolveContext = {
40 params?: Readonly<Record<string, string | undefined>>;
41};
42
43export type ResolveSecretMaterial = (input: {
44 ref: SecretRef;
45 context?: SecretMaterialResolveContext;
46}) => Effect.Effect<string, Error, never>;
47
48export type StoreSecretMaterial = (input: {
49 purpose: SecretMaterialPurpose;
50 value: string;
51 name?: string | null;
52}) => Effect.Effect<SecretRef, Error, never>;
53
54export type DeleteSecretMaterial = (
55 ref: SecretRef,
56) => Effect.Effect<boolean, Error, never>;
57
58export type UpdateSecretMaterial = (input: {
59 ref: SecretRef;
60 name?: string | null;
61 value?: string;
62}) => Effect.Effect<{
63 id: string;
64 providerId: string;
65 name: string | null;
66 purpose: string;
67 createdAt: number;
68 updatedAt: number;
69}, Error, never>;
70
71export class SecretMaterialResolverService extends Context.Tag(
72 "#runtime/SecretMaterialResolverService",
73)<SecretMaterialResolverService, ResolveSecretMaterial>() {}
74
75export class SecretMaterialStorerService extends Context.Tag(
76 "#runtime/SecretMaterialStorerService",
77)<SecretMaterialStorerService, StoreSecretMaterial>() {}
78
79export class SecretMaterialDeleterService extends Context.Tag(
80 "#runtime/SecretMaterialDeleterService",
81)<SecretMaterialDeleterService, DeleteSecretMaterial>() {}
82
83export class SecretMaterialUpdaterService extends Context.Tag(
84 "#runtime/SecretMaterialUpdaterService",
85)<SecretMaterialUpdaterService, UpdateSecretMaterial>() {}
86
87type SecretMaterialProviderRuntime = {51type SecretMaterialProviderRuntime = {
88 rows: ControlPlaneStoreShape;52 rows: ControlPlaneStoreShape;
89 env: NodeJS.ProcessEnv;53 env: NodeJS.ProcessEnv;
@@ -1085,17 +1049,16 @@ export const createDefaultSecretMaterialResolver = (input: { · 995 unmodified lines
1085 1049
1086export const createDefaultSecretMaterialStorer = (input: {1050export const createDefaultSecretMaterialStorer = (input: {
1087 rows: ControlPlaneStoreShape;1051 rows: ControlPlaneStoreShape;
1088 storeProviderId?: SecretStoreProviderId;
1089 dangerouslyAllowEnvSecrets?: boolean;1052 dangerouslyAllowEnvSecrets?: boolean;
1090 keychainServiceName?: string;1053 keychainServiceName?: string;
1091}): StoreSecretMaterial => {1054}): StoreSecretMaterial => {
1092 const providers = createSecretMaterialProviderRegistry();1055 const providers = createSecretMaterialProviderRegistry();
1093 const runtime = createSecretMaterialProviderRuntime(input);1056 const runtime = createSecretMaterialProviderRuntime(input);
1094 1057
1095 return ({ purpose, value, name }) =>1058 return ({ purpose, value, name, providerId }) =>
1096 Effect.gen(function* () {1059 Effect.gen(function* () {
1097 const defaultStoreProviderId = yield* resolveDefaultSecretStoreProviderId({1060 const defaultStoreProviderId = yield* resolveDefaultSecretStoreProviderId({
1098 storeProviderId: input.storeProviderId,1061 storeProviderId: parseSecretStoreProviderId(providerId) ?? undefined,
1099 env: runtime.env,1062 env: runtime.env,
1100 });1063 });
1101 const provider = yield* getSecretMaterialProvider({1064 const provider = yield* getSecretMaterialProvider({
@@ -1182,7 +1145,7 @@ const resolveRuntimeSecretMaterialConfig = (input: { · 80 unmodified lines
1182 ?? runtimeLocalWorkspace?.loadedConfig.config1145 ?? runtimeLocalWorkspace?.loadedConfig.config
1183 ?? null,1146 ?? null,
1184 workspaceRoot: input.workspaceRoot1147 workspaceRoot: input.workspaceRoot
1185 ?? runtimeLocalWorkspace?.context.workspaceRoot1148 ?? runtimeLocalWorkspace?.workspace.workspaceRoot
1186 ?? null,1149 ?? null,
1187 };1150 };
1188 });1151 });
@@ -1213,7 +1176,6 @@ export const SecretMaterialResolverLive = (input: { · 24 unmodified lines
1213 );1176 );
1214 1177
1215export const SecretMaterialStorerLive = (input: {1178export const SecretMaterialStorerLive = (input: {
1216 storeProviderId?: SecretStoreProviderId;
1217 dangerouslyAllowEnvSecrets?: boolean;1179 dangerouslyAllowEnvSecrets?: boolean;
1218 keychainServiceName?: string;1180 keychainServiceName?: string;
1219} = {}) =>1181} = {}) =>
@@ -1224,7 +1186,6 @@ export const SecretMaterialStorerLive = (input: { · 4 unmodified lines
1224 1186
1225 return createDefaultSecretMaterialStorer({1187 return createDefaultSecretMaterialStorer({
1226 rows,1188 rows,
1227 storeProviderId: input.storeProviderId,
1228 dangerouslyAllowEnvSecrets: input.dangerouslyAllowEnvSecrets,1189 dangerouslyAllowEnvSecrets: input.dangerouslyAllowEnvSecrets,
1229 keychainServiceName: input.keychainServiceName,1190 keychainServiceName: input.keychainServiceName,
1230 });1191 });
@@ -1267,7 +1228,6 @@ export const SecretMaterialUpdaterLive = (input: { · 36 unmodified lines
1267 1228
1268export const SecretMaterialLive = (input: {1229export const SecretMaterialLive = (input: {
1269 resolveSecretMaterial?: ResolveSecretMaterial;1230 resolveSecretMaterial?: ResolveSecretMaterial;
1270 storeProviderId?: SecretStoreProviderId;
1271 dangerouslyAllowEnvSecrets?: boolean;1231 dangerouslyAllowEnvSecrets?: boolean;
1272 keychainServiceName?: string;1232 keychainServiceName?: string;
1273 localConfig?: LocalExecutorConfig | null;1233 localConfig?: LocalExecutorConfig | null;
@@ -1279,3 +1239,46 @@ export const SecretMaterialLive = (input: { · 5 unmodified lines
1279 SecretMaterialDeleterLive(input),1239 SecretMaterialDeleterLive(input),
1280 SecretMaterialUpdaterLive(input),1240 SecretMaterialUpdaterLive(input),
1281 );1241 );
1242
1243export const createLocalInstanceConfigResolver = (): ResolveInstanceConfig => () => {
1244 const explicitDefaultStoreProvider = parseSecretStoreProviderId(
1245 process.env[SECRET_STORE_PROVIDER_ENV],
1246 );
1247 const providers = [
1248 {
1249 id: LOCAL_SECRET_PROVIDER_ID,
1250 name: "Local store",
1251 canStore: true,
1252 },
1253 ];
1254
1255 if (process.platform === "darwin" || process.platform === "linux") {
1256 providers.push({
1257 id: KEYCHAIN_SECRET_PROVIDER_ID,
1258 name:
1259 process.platform === "darwin" ? "macOS Keychain" : "Desktop Keyring",
1260 canStore:
1261 process.platform === "darwin" ||
1262 explicitDefaultStoreProvider === KEYCHAIN_SECRET_PROVIDER_ID,
1263 });
1264 }
1265
1266 providers.push({
1267 id: ENV_SECRET_PROVIDER_ID,
1268 name: "Environment variable",
1269 canStore: false,
1270 });
1271
1272 return resolveDefaultSecretStoreProviderId({
1273 storeProviderId: explicitDefaultStoreProvider ?? undefined,
1274 }).pipe(
1275 Effect.map((resolvedDefaultStoreProvider) => ({
1276 platform: process.platform,
1277 secretProviders: providers,
1278 defaultSecretStoreProvider: resolvedDefaultStoreProvider,
1279 })),
1280 );
1281};
1282
1283export const LocalInstanceConfigLive = () =>
1284 Layer.succeed(LocalInstanceConfigService, createLocalInstanceConfigResolver());
packages/platform/sdk-file/src/source-artifacts.test.tsrenamed from packages/platform/sdk/src/runtime/local/source-artifacts.test.ts+6−2
@@ -5,7 +5,11 @@ import { NodeFileSystem } from "@effect/platform-node"; · 4 unmodified lines
5import { describe, expect, it } from "@effect/vitest";5import { describe, expect, it } from "@effect/vitest";
6import * as Effect from "effect/Effect";6import * as Effect from "effect/Effect";
7 7
8import { SourceIdSchema, WorkspaceIdSchema, type Source } from "#schema";8import {
9 SourceIdSchema,
10 WorkspaceIdSchema,
11 type Source,
12} from "@executor/platform-sdk/schema";
9 13
10import {14import {
11 buildLocalSourceArtifact,15 buildLocalSourceArtifact,
@@ -76,7 +80,7 @@ const makeArtifact = () => { · 64 unmodified lines
76 documents: [80 documents: [
77 {81 {
78 documentKind: "openapi",82 documentKind: "openapi",
79 documentKey: source.binding.specUrl,83 documentKey: String(source.binding.specUrl),
80 contentText: '{"openapi":"3.1.0"}',84 contentText: '{"openapi":"3.1.0"}',
81 fetchedAt: 1,85 fetchedAt: 1,
82 },86 },
packages/platform/sdk-file/src/source-artifacts.ts+172−0
@@ -0,0 +1,172 @@
1import { join } from "node:path";
2import { FileSystem } from "@effect/platform";
3
4import type { NativeBlob } from "@executor/ir/model";
5import * as Effect from "effect/Effect";
6import type { Source } from "@executor/platform-sdk/schema";
7import type { SourceCatalogSyncResult } from "@executor/source-core";
8import {
9 buildLocalSourceArtifact,
10 decodeStoredLocalSourceArtifact,
11 hydrateLocalSourceArtifactDocuments,
12 splitLocalSourceArtifactDocuments,
13 type LocalSourceArtifact,
14} from "../../sdk/src/runtime/source-artifacts";
15import type { ResolvedLocalWorkspaceContext } from "./config";
16import {
17 LocalFileSystemError,
18 unknownLocalErrorDetails,
19} from "./errors";
20
21const mapFileSystemError = (path: string, action: string) => (cause: unknown) =>
22 new LocalFileSystemError({
23 message: `Failed to ${action} ${path}: ${unknownLocalErrorDetails(cause)}`,
24 action,
25 path,
26 details: unknownLocalErrorDetails(cause),
27 });
28
29const localSourceArtifactPath = (input: {
30 context: ResolvedLocalWorkspaceContext;
31 sourceId: string;
32}): string =>
33 join(input.context.artifactsDirectory, "sources", `${input.sourceId}.json`);
34
35const localSourceDocumentDirectory = (input: {
36 context: ResolvedLocalWorkspaceContext;
37 sourceId: string;
38}): string =>
39 join(input.context.artifactsDirectory, "sources", input.sourceId, "documents");
40
41const localSourceDocumentPath = (input: {
42 context: ResolvedLocalWorkspaceContext;
43 sourceId: string;
44 documentId: string;
45}): string =>
46 join(localSourceDocumentDirectory(input), `${input.documentId}.txt`);
47
48export { buildLocalSourceArtifact };
49export type { LocalSourceArtifact };
50
51export const readLocalSourceArtifact = (input: {
52 context: ResolvedLocalWorkspaceContext;
53 sourceId: string;
54}): Effect.Effect<
55 LocalSourceArtifact | null,
56 LocalFileSystemError,
57 FileSystem.FileSystem
58> =>
59 Effect.gen(function* () {
60 const fs = yield* FileSystem.FileSystem;
61 const path = localSourceArtifactPath(input);
62 const exists = yield* fs.exists(path).pipe(
63 Effect.mapError(mapFileSystemError(path, "check source artifact path")),
64 );
65 if (!exists) {
66 return null;
67 }
68
69 const content = yield* fs.readFileString(path, "utf8").pipe(
70 Effect.mapError(mapFileSystemError(path, "read source artifact")),
71 );
72 const decodedSnapshotArtifact = decodeStoredLocalSourceArtifact(content);
73 if (decodedSnapshotArtifact === null) {
74 return null;
75 }
76
77 const rawDocuments: Record<string, NativeBlob> = {};
78 for (const documentId of Object.keys(decodedSnapshotArtifact.snapshot.catalog.documents)) {
79 const sourceDocumentPath = localSourceDocumentPath({
80 context: input.context,
81 sourceId: input.sourceId,
82 documentId,
83 });
84 const sourceDocumentExists = yield* fs.exists(sourceDocumentPath).pipe(
85 Effect.mapError(mapFileSystemError(sourceDocumentPath, "check source document path")),
86 );
87 if (!sourceDocumentExists) {
88 continue;
89 }
90
91 const sourceContent = yield* fs.readFileString(sourceDocumentPath, "utf8").pipe(
92 Effect.mapError(mapFileSystemError(sourceDocumentPath, "read source document")),
93 );
94 rawDocuments[documentId] = {
95 sourceKind: decodedSnapshotArtifact.snapshot.import.sourceKind,
96 kind: "source_document",
97 value: sourceContent,
98 } satisfies NativeBlob;
99 }
100
101 return Object.keys(rawDocuments).length > 0
102 ? hydrateLocalSourceArtifactDocuments({
103 artifact: decodedSnapshotArtifact,
104 rawDocuments,
105 })
106 : decodedSnapshotArtifact;
107 });
108
109export const writeLocalSourceArtifact = (input: {
110 context: ResolvedLocalWorkspaceContext;
111 sourceId: string;
112 artifact: LocalSourceArtifact;
113}): Effect.Effect<void, LocalFileSystemError, FileSystem.FileSystem> =>
114 Effect.gen(function* () {
115 const fs = yield* FileSystem.FileSystem;
116 const directory = join(input.context.artifactsDirectory, "sources");
117 const path = localSourceArtifactPath(input);
118 const sourceDocumentDirectory = localSourceDocumentDirectory(input);
119 const split = splitLocalSourceArtifactDocuments(input.artifact);
120
121 yield* fs.makeDirectory(directory, { recursive: true }).pipe(
122 Effect.mapError(mapFileSystemError(directory, "create source artifact directory")),
123 );
124 yield* fs.remove(sourceDocumentDirectory, { recursive: true, force: true }).pipe(
125 Effect.mapError(
126 mapFileSystemError(sourceDocumentDirectory, "remove source document directory"),
127 ),
128 );
129 if (split.rawDocuments.length > 0) {
130 yield* fs.makeDirectory(sourceDocumentDirectory, { recursive: true }).pipe(
131 Effect.mapError(
132 mapFileSystemError(sourceDocumentDirectory, "create source document directory"),
133 ),
134 );
135 for (const rawDocument of split.rawDocuments) {
136 const sourceDocumentPath = localSourceDocumentPath({
137 context: input.context,
138 sourceId: input.sourceId,
139 documentId: rawDocument.documentId,
140 });
141 yield* fs.writeFileString(sourceDocumentPath, rawDocument.content).pipe(
142 Effect.mapError(mapFileSystemError(sourceDocumentPath, "write source document")),
143 );
144 }
145 }
146 yield* fs.writeFileString(path, `${JSON.stringify(split.artifact)}\n`).pipe(
147 Effect.mapError(mapFileSystemError(path, "write source artifact")),
148 );
149 });
150
151export const removeLocalSourceArtifact = (input: {
152 context: ResolvedLocalWorkspaceContext;
153 sourceId: string;
154}): Effect.Effect<void, LocalFileSystemError, FileSystem.FileSystem> =>
155 Effect.gen(function* () {
156 const fs = yield* FileSystem.FileSystem;
157 const path = localSourceArtifactPath(input);
158 const exists = yield* fs.exists(path).pipe(
159 Effect.mapError(mapFileSystemError(path, "check source artifact path")),
160 );
161 if (exists) {
162 yield* fs.remove(path).pipe(
163 Effect.mapError(mapFileSystemError(path, "remove source artifact")),
164 );
165 }
166 const sourceDocumentDirectory = localSourceDocumentDirectory(input);
167 yield* fs.remove(sourceDocumentDirectory, { recursive: true, force: true }).pipe(
168 Effect.mapError(
169 mapFileSystemError(sourceDocumentDirectory, "remove source document directory"),
170 ),
171 );
172 });
packages/platform/sdk-file/src/source-type-declarations.test.tsrenamed from packages/platform/sdk/src/runtime/catalog/source/type-declarations.test.ts+6−2
@@ -5,7 +5,11 @@ import { NodeFileSystem } from "@effect/platform-node"; · 4 unmodified lines
5import { describe, expect, it } from "@effect/vitest";5import { describe, expect, it } from "@effect/vitest";
6import * as Effect from "effect/Effect";6import * as Effect from "effect/Effect";
7 7
8import { SourceIdSchema, WorkspaceIdSchema, type Source } from "#schema";8import {
9 SourceIdSchema,
10 WorkspaceIdSchema,
11 type Source,
12} from "@executor/platform-sdk/schema";
9 13
10import {14import {
11 createCatalogSnapshotV1,15 createCatalogSnapshotV1,
@@ -21,7 +25,7 @@ import { · 9 unmodified lines
21 ShapeSymbolIdSchema,25 ShapeSymbolIdSchema,
22} from "@executor/ir/ids";26} from "@executor/ir/ids";
23import type { CatalogV1, ProvenanceRef } from "@executor/ir/model";27import type { CatalogV1, ProvenanceRef } from "@executor/ir/model";
24import { syncWorkspaceSourceTypeDeclarationsNode } from "./type-declarations";28import { syncWorkspaceSourceTypeDeclarationsNode } from "./source-type-declarations";
25 29
26const put = <K extends string, V>(record: Record<K, V>, key: K, value: V) => {30const put = <K extends string, V>(record: Record<K, V>, key: K, value: V) => {
27 record[key] = value;31 record[key] = value;
packages/platform/sdk-file/src/source-type-declarations.ts+427−0
@@ -0,0 +1,427 @@
1import { join } from "node:path";
2import { FileSystem } from "@effect/platform";
3import { NodeFileSystem } from "@effect/platform-node";
4import type { Source } from "@executor/platform-sdk/schema";
5import * as Cause from "effect/Cause";
6import * as Effect from "effect/Effect";
7import * as Fiber from "effect/Fiber";
8import type { CatalogSnapshotV1 } from "@executor/ir/model";
9
10import { projectCatalogForAgentSdk } from "@executor/ir/catalog";
11import {
12 createCatalogTypeProjector,
13 formatPropertyKey,
14 joinTypeNameSegments,
15 projectedCatalogTypeRoots,
16 shapeAllowsOmittedArgs,
17} from "../../sdk/src/runtime/catalog/catalog-typescript";
18import type { ResolvedLocalWorkspaceContext } from "./config";
19import {
20 LocalFileSystemError,
21 unknownLocalErrorDetails,
22} from "./errors";
23
24type SourceDeclarationEntry = {
25 source: Source;
26 snapshot: CatalogSnapshotV1;
27};
28
29type ToolMethodNode = {
30 readonly segments: readonly string[];
31 readonly inputType: string;
32 readonly outputType: string;
33 readonly argsOptional: boolean;
34};
35
36type SourceDeclarationModel = {
37 readonly methods: readonly ToolMethodNode[];
38 readonly supportingTypes: readonly string[];
39};
40
41type ToolTreeNode = {
42 method: ToolMethodNode | null;
43 children: Map<string, ToolTreeNode>;
44};
45
46type SourceDeclarationStub = {
47 sourceId: string;
48};
49
50const GENERATED_TYPES_DIRECTORY = "types";
51const GENERATED_SOURCE_TYPES_DIRECTORY = "sources";
52
53const mapFileSystemError = (path: string, action: string) => (cause: unknown) =>
54 new LocalFileSystemError({
55 message: `Failed to ${action} ${path}: ${unknownLocalErrorDetails(cause)}`,
56 action,
57 path,
58 details: unknownLocalErrorDetails(cause),
59 });
60
61const declarationDirectory = (context: ResolvedLocalWorkspaceContext): string =>
62 join(context.configDirectory, GENERATED_TYPES_DIRECTORY);
63
64const sourceDeclarationDirectory = (context: ResolvedLocalWorkspaceContext): string =>
65 join(declarationDirectory(context), GENERATED_SOURCE_TYPES_DIRECTORY);
66
67const sourceDeclarationFileName = (sourceId: string): string => `${sourceId}.d.ts`;
68
69const sourceDeclarationPath = (context: ResolvedLocalWorkspaceContext, sourceId: string): string =>
70 join(sourceDeclarationDirectory(context), sourceDeclarationFileName(sourceId));
71
72const aggregateDeclarationPath = (context: ResolvedLocalWorkspaceContext): string =>
73 join(declarationDirectory(context), "index.d.ts");
74
75const sourceTypeInterfaceName = (sourceId: string): string =>
76 `SourceTools_${sourceId.replace(/[^A-Za-z0-9_$]+/g, "_")}`;
77
78const methodSignature = (method: ToolMethodNode): string =>
79 `(${method.argsOptional ? "args?:" : "args:"} ${method.inputType}) => Promise<${method.outputType}>`;
80
81const createToolTreeNode = (): ToolTreeNode => ({
82 method: null,
83 children: new Map(),
84});
85
86const objectTypeLiteral = (
87 lines: readonly string[],
88 indent: string,
89): string => {
90 if (lines.length === 0) {
91 return "{}";
92 }
93
94 return [
95 "{",
96 ...lines.map((line) => `${indent}${line}`),
97 `${indent.slice(0, -2)}}`,
98 ].join("\n");
99};
100
101const renderToolTreeType = (node: ToolTreeNode, indentLevel: number): string => {
102 const indent = " ".repeat(indentLevel + 1);
103 const childLines = [...node.children.entries()]
104 .sort(([left], [right]) => left.localeCompare(right))
105 .map(([segment, child]) =>
106 `${formatPropertyKey(segment)}: ${renderToolTreeType(child, indentLevel + 1)};`
107 );
108
109 const objectType = objectTypeLiteral(childLines, indent);
110 if (node.method === null) {
111 return objectType;
112 }
113
114 const callable = methodSignature(node.method);
115 return node.children.size === 0
116 ? callable
117 : `(${callable}) & ${objectType}`;
118};
119
120const buildToolTree = (methods: readonly ToolMethodNode[]): ToolTreeNode => {
121 const root = createToolTreeNode();
122
123 for (const method of methods) {
124 let current = root;
125 for (const segment of method.segments) {
126 const existing = current.children.get(segment);
127 if (existing) {
128 current = existing;
129 continue;
130 }
131
132 const next = createToolTreeNode();
133 current.children.set(segment, next);
134 current = next;
135 }
136
137 current.method = method;
138 }
139
140 return root;
141};
142
143const buildSourceDeclarationModel = (snapshot: CatalogSnapshotV1): SourceDeclarationModel => {
144 const projected = projectCatalogForAgentSdk({
145 catalog: snapshot.catalog,
146 });
147 const toolDescriptors = Object.values(projected.toolDescriptors)
148 .sort((left, right) => left.toolPath.join(".").localeCompare(right.toolPath.join(".")));
149 const renderer = createCatalogTypeProjector({
150 catalog: projected.catalog,
151 roots: projectedCatalogTypeRoots(projected),
152 });
153
154 const methods = toolDescriptors
155 .map((descriptor) => ({
156 segments: descriptor.toolPath,
157 inputType: renderer.renderDeclarationShape(descriptor.callShapeId, {
158 aliasHint: joinTypeNameSegments(...descriptor.toolPath, "call"),
159 }),
160 outputType: descriptor.resultShapeId
161 ? renderer.renderDeclarationShape(descriptor.resultShapeId, {
162 aliasHint: joinTypeNameSegments(...descriptor.toolPath, "result"),
163 })
164 : "unknown",
165 argsOptional: shapeAllowsOmittedArgs(projected.catalog, descriptor.callShapeId),
166 }) satisfies ToolMethodNode);
167
168 return {
169 methods,
170 supportingTypes: renderer.supportingDeclarations(),
171 };
172};
173
174const sourceDeclarationText = (entry: SourceDeclarationEntry): string => {
175 const interfaceName = sourceTypeInterfaceName(entry.source.id);
176 const declarationModel = buildSourceDeclarationModel(entry.snapshot);
177 const tree = buildToolTree(declarationModel.methods);
178 const body = renderToolTreeType(tree, 0);
179
180 return [
181 "// Generated by executor. Do not edit by hand.",
182 `// Source: ${entry.source.name} (${entry.source.id})`,
183 "",
184 ...declarationModel.supportingTypes,
185 ...(declarationModel.supportingTypes.length > 0 ? [""] : []),
186 `export interface ${interfaceName} ${body}`,
187 "",
188 `export declare const tools: ${interfaceName};`,
189 `export type ${interfaceName}Tools = ${interfaceName};`,
190 "export default tools;",
191 "",
192 ].join("\n");
193};
194
195const aggregateDeclarationText = (entries: readonly SourceDeclarationEntry[]): string => {
196 return aggregateDeclarationTextFromSourceIds(
197 entries.map((entry) => ({ sourceId: entry.source.id })),
198 );
199};
200
201const aggregateDeclarationTextFromSourceIds = (entries: readonly SourceDeclarationStub[]): string => {
202 const sorted = [...entries].sort((left, right) => left.sourceId.localeCompare(right.sourceId));
203 const imports = sorted.map((entry) =>
204 `import type { ${sourceTypeInterfaceName(entry.sourceId)} } from "../sources/${entry.sourceId}";`
205 );
206 const intersections = sorted.map((entry) => sourceTypeInterfaceName(entry.sourceId));
207 const executorToolsType = intersections.length > 0
208 ? intersections.join(" & ")
209 : "{}";
210
211 return [
212 "// Generated by executor. Do not edit by hand.",
213 ...imports,
214 ...(imports.length > 0 ? [""] : []),
215 `export type ExecutorSourceTools = ${executorToolsType};`,
216 "",
217 "declare global {",
218 " const tools: ExecutorSourceTools;",
219 "}",
220 "",
221 "export declare const tools: ExecutorSourceTools;",
222 "export default tools;",
223 "",
224 ].join("\n");
225};
226
227export const syncWorkspaceSourceTypeDeclarations = (input: {
228 context: ResolvedLocalWorkspaceContext;
229 entries: readonly SourceDeclarationEntry[];
230}): Effect.Effect<void, LocalFileSystemError, FileSystem.FileSystem> =>
231 Effect.gen(function* () {
232 const fs = yield* FileSystem.FileSystem;
233 const declarationsDir = declarationDirectory(input.context);
234 const perSourceDir = sourceDeclarationDirectory(input.context);
235 const activeEntries = input.entries
236 .filter((entry) => entry.source.enabled && entry.source.status === "connected")
237 .sort((left, right) => left.source.id.localeCompare(right.source.id));
238
239 yield* fs.makeDirectory(declarationsDir, { recursive: true }).pipe(
240 Effect.mapError(mapFileSystemError(declarationsDir, "create declaration directory")),
241 );
242 yield* fs.makeDirectory(perSourceDir, { recursive: true }).pipe(
243 Effect.mapError(mapFileSystemError(perSourceDir, "create source declaration directory")),
244 );
245
246 const expectedFiles = new Set(
247 activeEntries.map((entry) => sourceDeclarationFileName(entry.source.id)),
248 );
249 const existingFiles = yield* fs.readDirectory(perSourceDir).pipe(
250 Effect.mapError(mapFileSystemError(perSourceDir, "read source declaration directory")),
251 );
252
253 for (const existingFile of existingFiles) {
254 if (expectedFiles.has(existingFile)) {
255 continue;
256 }
257
258 const stalePath = join(perSourceDir, existingFile);
259 yield* fs.remove(stalePath).pipe(
260 Effect.mapError(mapFileSystemError(stalePath, "remove stale source declaration")),
261 );
262 }
263
264 for (const entry of activeEntries) {
265 const filePath = sourceDeclarationPath(input.context, entry.source.id);
266 yield* fs.writeFileString(filePath, sourceDeclarationText(entry)).pipe(
267 Effect.mapError(mapFileSystemError(filePath, "write source declaration")),
268 );
269 }
270
271 const aggregatePath = aggregateDeclarationPath(input.context);
272 yield* fs.writeFileString(
273 aggregatePath,
274 aggregateDeclarationText(activeEntries),
275 ).pipe(
276 Effect.mapError(mapFileSystemError(aggregatePath, "write aggregate declaration")),
277 );
278 });
279
280export const syncSourceTypeDeclaration = (input: {
281 context: ResolvedLocalWorkspaceContext;
282 source: Source;
283 snapshot: CatalogSnapshotV1 | null;
284}): Effect.Effect<void, LocalFileSystemError, FileSystem.FileSystem> =>
285 Effect.gen(function* () {
286 const fs = yield* FileSystem.FileSystem;
287 const declarationsDir = declarationDirectory(input.context);
288 const perSourceDir = sourceDeclarationDirectory(input.context);
289
290 yield* fs.makeDirectory(declarationsDir, { recursive: true }).pipe(
291 Effect.mapError(mapFileSystemError(declarationsDir, "create declaration directory")),
292 );
293 yield* fs.makeDirectory(perSourceDir, { recursive: true }).pipe(
294 Effect.mapError(mapFileSystemError(perSourceDir, "create source declaration directory")),
295 );
296
297 const filePath = sourceDeclarationPath(input.context, input.source.id);
298 const shouldWrite = input.source.enabled
299 && input.source.status === "connected"
300 && input.snapshot !== null;
301
302 if (shouldWrite) {
303 const snapshot = input.snapshot;
304 if (snapshot === null) {
305 return;
306 }
307 yield* fs.writeFileString(
308 filePath,
309 sourceDeclarationText({
310 source: input.source,
311 snapshot,
312 }),
313 ).pipe(
314 Effect.mapError(mapFileSystemError(filePath, "write source declaration")),
315 );
316 } else {
317 const exists = yield* fs.exists(filePath).pipe(
318 Effect.mapError(mapFileSystemError(filePath, "check source declaration path")),
319 );
320 if (exists) {
321 yield* fs.remove(filePath).pipe(
322 Effect.mapError(mapFileSystemError(filePath, "remove source declaration")),
323 );
324 }
325 }
326
327 const sourceIds = (yield* fs.readDirectory(perSourceDir).pipe(
328 Effect.mapError(mapFileSystemError(perSourceDir, "read source declaration directory")),
329 ))
330 .filter((fileName) => fileName.endsWith(".d.ts"))
331 .map((fileName) => ({ sourceId: fileName.slice(0, -".d.ts".length) }));
332
333 const aggregatePath = aggregateDeclarationPath(input.context);
334 yield* fs.writeFileString(
335 aggregatePath,
336 aggregateDeclarationTextFromSourceIds(sourceIds),
337 ).pipe(
338 Effect.mapError(mapFileSystemError(aggregatePath, "write aggregate declaration")),
339 );
340 });
341
342export const syncWorkspaceSourceTypeDeclarationsNode = (input: {
343 context: ResolvedLocalWorkspaceContext;
344 entries: readonly SourceDeclarationEntry[];
345}): Effect.Effect<void, LocalFileSystemError, never> =>
346 syncWorkspaceSourceTypeDeclarations(input).pipe(Effect.provide(NodeFileSystem.layer));
347
348export const syncSourceTypeDeclarationNode = (input: {
349 context: ResolvedLocalWorkspaceContext;
350 source: Source;
351 snapshot: CatalogSnapshotV1 | null;
352}): Effect.Effect<void, LocalFileSystemError, never> =>
353 syncSourceTypeDeclaration(input).pipe(Effect.provide(NodeFileSystem.layer));
354
355const logBackgroundDeclarationError = (label: string, cause: unknown): void => {
356 const message = Cause.isCause(cause)
357 ? Cause.pretty(cause)
358 : cause instanceof Error
359 ? cause.message
360 : String(cause);
361 console.warn(`[source-types] ${label} failed: ${message}`);
362};
363
364const BACKGROUND_DECLARATION_REFRESH_DELAY = "1500 millis";
365const workspaceRefreshFibers = new Map<string, Fiber.RuntimeFiber<void, never>>();
366const sourceRefreshFibers = new Map<string, Fiber.RuntimeFiber<void, never>>();
367
368export const refreshWorkspaceSourceTypeDeclarationsInBackground = (input: {
369 context: ResolvedLocalWorkspaceContext;
370 entries: readonly SourceDeclarationEntry[];
371}): void => {
372 const key = input.context.configDirectory;
373 const existingFiber = workspaceRefreshFibers.get(key);
374 if (existingFiber) {
375 Effect.runFork(Fiber.interruptFork(existingFiber));
376 }
377
378 const fiber = Effect.runFork(
379 Effect.sleep(BACKGROUND_DECLARATION_REFRESH_DELAY).pipe(
380 Effect.zipRight(
381 syncWorkspaceSourceTypeDeclarationsNode(input).pipe(
382 Effect.catchAllCause((cause) =>
383 Effect.sync(() => {
384 logBackgroundDeclarationError("workspace declaration refresh", cause);
385 })
386 ),
387 ),
388 ),
389 ),
390 );
391
392 workspaceRefreshFibers.set(key, fiber);
393 fiber.addObserver(() => {
394 workspaceRefreshFibers.delete(key);
395 });
396};
397
398export const refreshSourceTypeDeclarationInBackground = (input: {
399 context: ResolvedLocalWorkspaceContext;
400 source: Source;
401 snapshot: CatalogSnapshotV1 | null;
402}): void => {
403 const key = `${input.context.configDirectory}:${input.source.id}`;
404 const existingFiber = sourceRefreshFibers.get(key);
405 if (existingFiber) {
406 Effect.runFork(Fiber.interruptFork(existingFiber));
407 }
408
409 const fiber = Effect.runFork(
410 Effect.sleep(BACKGROUND_DECLARATION_REFRESH_DELAY).pipe(
411 Effect.zipRight(
412 syncSourceTypeDeclarationNode(input).pipe(
413 Effect.catchAllCause((cause) =>
414 Effect.sync(() => {
415 logBackgroundDeclarationError(`source ${input.source.id} declaration refresh`, cause);
416 })
417 ),
418 ),
419 ),
420 ),
421 );
422
423 sourceRefreshFibers.set(key, fiber);
424 fiber.addObserver(() => {
425 sourceRefreshFibers.delete(key);
426 });
427};
packages/platform/sdk-file/src/tools.tsrenamed from packages/platform/sdk/src/runtime/local/tools.ts+1−41
@@ -15,11 +15,10 @@ import { · 14 unmodified lines
15 type ToolMetadata,15 type ToolMetadata,
16 type ToolPath,16 type ToolPath,
17} from "@executor/codemode-core";17} from "@executor/codemode-core";
18import * as Context from "effect/Context";
19import * as Effect from "effect/Effect";18import * as Effect from "effect/Effect";
20import * as Layer from "effect/Layer";
21import * as ts from "typescript";19import * as ts from "typescript";
22 20
21import type { LocalToolRuntime } from "../../sdk/src/runtime/local-tool-runtime";
23import type { ResolvedLocalWorkspaceContext } from "./config";22import type { ResolvedLocalWorkspaceContext } from "./config";
24import {23import {
25 LocalFileSystemError,24 LocalFileSystemError,
@@ -79,13 +78,6 @@ export const defineLocalTool = < · 53 unmodified lines
79 | LocalToolWithMetadata<TInputSchema, TOutputSchema>,78 | LocalToolWithMetadata<TInputSchema, TOutputSchema>,
80): typeof input => input;79): typeof input => input;
81 80
82export type LocalToolRuntime = {
83 tools: ToolMap;
84 catalog: ToolCatalog;
85 toolInvoker: ToolInvoker;
86 toolPaths: Set<string>;
87};
88
89const emptyLocalToolRuntime = (): LocalToolRuntime => {81const emptyLocalToolRuntime = (): LocalToolRuntime => {
90 const tools: ToolMap = {};82 const tools: ToolMap = {};
91 return {83 return {
@@ -651,35 +643,3 @@ export const loadLocalToolRuntime = ( · 559 unmodified lines
651 toolPaths: new Set(Object.keys(tools)),643 toolPaths: new Set(Object.keys(tools)),
652 } satisfies LocalToolRuntime;644 } satisfies LocalToolRuntime;
653 });645 });
654
655export type LocalToolRuntimeLoaderShape = {
656 load: (
657 context: ResolvedLocalWorkspaceContext,
658 ) => Effect.Effect<
659 LocalToolRuntime,
660 | LocalFileSystemError
661 | LocalToolDefinitionError
662 | LocalToolImportError
663 | LocalToolPathConflictError
664 | LocalToolTranspileError,
665 never
666 >;
667};
668
669export class LocalToolRuntimeLoaderService extends Context.Tag(
670 "#runtime/LocalToolRuntimeLoaderService",
671)<LocalToolRuntimeLoaderService, LocalToolRuntimeLoaderShape>() {}
672
673export const LocalToolRuntimeLoaderLive = Layer.effect(
674 LocalToolRuntimeLoaderService,
675 Effect.gen(function* () {
676 const fileSystem = yield* FileSystem.FileSystem;
677
678 return LocalToolRuntimeLoaderService.of({
679 load: (context) =>
680 loadLocalToolRuntime(context).pipe(
681 Effect.provideService(FileSystem.FileSystem, fileSystem),
682 ),
683 });
684 }),
685);
packages/platform/sdk-file/src/workspace-state.tsrenamed from packages/platform/sdk/src/runtime/local/workspace-state.ts+14−52
@@ -1,14 +1,13 @@
1import { join } from "node:path";1import { join } from "node:path";
2import { FileSystem } from "@effect/platform";2import { FileSystem } from "@effect/platform";
3
4import {
5 PolicyIdSchema,
6 SourceStatusSchema,
7 TimestampMsSchema,
8} from "#schema";
9import * as Effect from "effect/Effect";3import * as Effect from "effect/Effect";
10import * as Schema from "effect/Schema";4import * as Schema from "effect/Schema";
11 5
6import type { LocalWorkspaceState } from "../../sdk/src/runtime/workspace-state";
7import {
8 defaultLocalWorkspaceState,
9 LocalWorkspaceStateSchema,
10} from "../../sdk/src/runtime/workspace-state";
12import type { ResolvedLocalWorkspaceContext } from "./config";11import type { ResolvedLocalWorkspaceContext } from "./config";
13import {12import {
14 LocalFileSystemError,13 LocalFileSystemError,
@@ -18,36 +17,6 @@ import { · 3 unmodified lines
18 17
19const WORKSPACE_STATE_BASENAME = "workspace-state.json";18const WORKSPACE_STATE_BASENAME = "workspace-state.json";
20 19
21const LocalWorkspaceSourceStateSchema = Schema.Struct({
22 status: SourceStatusSchema,
23 lastError: Schema.NullOr(Schema.String),
24 sourceHash: Schema.NullOr(Schema.String),
25 createdAt: TimestampMsSchema,
26 updatedAt: TimestampMsSchema,
27});
28
29const LocalWorkspacePolicyStateSchema = Schema.Struct({
30 id: PolicyIdSchema,
31 createdAt: TimestampMsSchema,
32 updatedAt: TimestampMsSchema,
33});
34
35export const LocalWorkspaceStateSchema = Schema.Struct({
36 version: Schema.Literal(1),
37 sources: Schema.Record({
38 key: Schema.String,
39 value: LocalWorkspaceSourceStateSchema,
40 }),
41 policies: Schema.Record({
42 key: Schema.String,
43 value: LocalWorkspacePolicyStateSchema,
44 }),
45});
46
47export type LocalWorkspaceSourceState = typeof LocalWorkspaceSourceStateSchema.Type;
48export type LocalWorkspacePolicyState = typeof LocalWorkspacePolicyStateSchema.Type;
49export type LocalWorkspaceState = typeof LocalWorkspaceStateSchema.Type;
50
51const decodeLocalWorkspaceState = Schema.decodeUnknownSync(LocalWorkspaceStateSchema);20const decodeLocalWorkspaceState = Schema.decodeUnknownSync(LocalWorkspaceStateSchema);
52 21
53const mapFileSystemError = (path: string, action: string) => (cause: unknown) =>22const mapFileSystemError = (path: string, action: string) => (cause: unknown) =>
@@ -58,12 +27,6 @@ const mapFileSystemError = (path: string, action: string) => (cause: unknown) => · 4 unmodified lines
58 details: unknownLocalErrorDetails(cause),27 details: unknownLocalErrorDetails(cause),
59 });28 });
60 29
61const defaultLocalWorkspaceState = (): LocalWorkspaceState => ({
62 version: 1,
63 sources: {},
64 policies: {},
65});
66
67export const localWorkspaceStatePath = (30export const localWorkspaceStatePath = (
68 context: ResolvedLocalWorkspaceContext,31 context: ResolvedLocalWorkspaceContext,
69): string => join(context.stateDirectory, WORKSPACE_STATE_BASENAME);32): string => join(context.stateDirectory, WORKSPACE_STATE_BASENAME);
@@ -90,13 +53,12 @@ export const loadLocalWorkspaceState = ( · 20 unmodified lines
90 );53 );
91 return yield* Effect.try({54 return yield* Effect.try({
92 try: () => decodeLocalWorkspaceState(JSON.parse(content) as unknown),55 try: () => decodeLocalWorkspaceState(JSON.parse(content) as unknown),
93 catch: (cause) => {56 catch: (cause) =>
94 return new LocalWorkspaceStateDecodeError({57 new LocalWorkspaceStateDecodeError({
95 message: `Invalid local workspace state at ${path}: ${unknownLocalErrorDetails(cause)}`,58 message: `Invalid local workspace state at ${path}: ${unknownLocalErrorDetails(cause)}`,
96 path,59 path,
97 details: unknownLocalErrorDetails(cause),60 details: unknownLocalErrorDetails(cause),
98 });61 }),
99 },
100 });62 });
101 });63 });
102 64
@@ -107,12 +69,12 @@ export const writeLocalWorkspaceState = (input: { · 4 unmodified lines
107 Effect.gen(function* () {69 Effect.gen(function* () {
108 const fs = yield* FileSystem.FileSystem;70 const fs = yield* FileSystem.FileSystem;
109 yield* fs.makeDirectory(input.context.stateDirectory, { recursive: true }).pipe(71 yield* fs.makeDirectory(input.context.stateDirectory, { recursive: true }).pipe(
110 Effect.mapError(mapFileSystemError(input.context.stateDirectory, "create state directory")),72 Effect.mapError(
73 mapFileSystemError(input.context.stateDirectory, "create state directory"),
74 ),
111 );75 );
112 yield* fs.writeFileString(76 const path = localWorkspaceStatePath(input.context);
113 localWorkspaceStatePath(input.context),77 yield* fs.writeFileString(path, `${JSON.stringify(input.state, null, 2)}\n`).pipe(
114 `${JSON.stringify(input.state, null, 2)}\n`,78 Effect.mapError(mapFileSystemError(path, "write workspace state")),
115 ).pipe(
116 Effect.mapError(mapFileSystemError(localWorkspaceStatePath(input.context), "write workspace state")),
117 );79 );
118 });80 });
packages/platform/sdk-file/tsconfig.json+13−0
@@ -0,0 +1,13 @@
1{
2 "compilerOptions": {
3 "target": "ES2022",
4 "module": "ESNext",
5 "moduleResolution": "Bundler",
6 "strict": true,
7 "skipLibCheck": true,
8 "noEmit": true,
9 "lib": ["ES2022"],
10 "types": ["bun-types", "node"]
11 },
12 "include": ["src/**/*.ts"]
13}
packages/platform/sdk/package.json+0−1
@@ -18,7 +18,6 @@ · 17 unmodified lines
18 "#schema": "./src/schema/index.ts"18 "#schema": "./src/schema/index.ts"
19 },19 },
20 "scripts": {20 "scripts": {
21 "fixture:release:capture": "bun run ./src/runtime/local/capture-release-workspace-fixture.ts",
22 "test": "vitest run",21 "test": "vitest run",
23 "test:watch": "vitest",22 "test:watch": "vitest",
24 "typecheck": "bunx tsc --noEmit -p tsconfig.json"23 "typecheck": "bunx tsc --noEmit -p tsconfig.json"
packages/platform/sdk/src/backend.ts+4−0
@@ -12,6 +12,10 @@ import { · 11 unmodified lines
12 type RuntimeControlPlaneServices,12 type RuntimeControlPlaneServices,
13 type RuntimeSecretMaterialServices,13 type RuntimeSecretMaterialServices,
14} from "./runtime";14} from "./runtime";
15export type {
16 ExecutorWorkspaceContext,
17 ExecutorWorkspaceDescriptor,
18} from "./workspace";
15 19
16export type ExecutorBackend = {20export type ExecutorBackend = {
17 createRuntime: (21 createRuntime: (
packages/platform/sdk/src/local/operations.ts+9−23
@@ -14,12 +14,7 @@ import * as Option from "effect/Option"; · 13 unmodified lines
14import { submitExecutionInteractionResponse } from "../runtime/execution/service";14import { submitExecutionInteractionResponse } from "../runtime/execution/service";
15import { LiveExecutionManagerService } from "../runtime/execution/live";15import { LiveExecutionManagerService } from "../runtime/execution/live";
16import { operationErrors } from "../runtime/policy/operation-errors";16import { operationErrors } from "../runtime/policy/operation-errors";
17import { resolveLocalWorkspaceContext } from "../runtime/local/config";17import { InstallationStore } from "../runtime/workspace/storage";
18import {
19 InstallationStore,
20 LocalInstallationStore,
21} from "../runtime/local/storage";
22import { getRuntimeLocalWorkspaceOption } from "../runtime/local/runtime-context";
23import { RuntimeSourceAuthServiceTag } from "../runtime/sources/source-auth-service";18import { RuntimeSourceAuthServiceTag } from "../runtime/sources/source-auth-service";
24import {19import {
25 createSourceCredentialSelectionBearerContent,20 createSourceCredentialSelectionBearerContent,
@@ -190,24 +185,15 @@ const loadSourceCredentialInteraction = (input: { · 164 unmodified lines
190 185
191export const getLocalInstallation = () =>186export const getLocalInstallation = () =>
192 Effect.gen(function* () {187 Effect.gen(function* () {
193 const runtimeLocalWorkspace = yield* getRuntimeLocalWorkspaceOption();188 const installationStore = yield* InstallationStore;
194 const context =189 return yield* installationStore.load().pipe(
195 runtimeLocalWorkspace?.context ??190 Effect.mapError((error) =>
196 (yield* resolveLocalWorkspaceContext().pipe(191 localOps.installation.unknownStorage(
197 Effect.mapError((error) =>192 error,
198 localOps.installation.unknownStorage(193 "Failed loading local installation",
199 error,
200 "Failed resolving local workspace context",
201 ),
202 ),194 ),
203 ));195 ),
204 196 );
205 const installationStore =
206 runtimeLocalWorkspace !== null
207 ? yield* InstallationStore
208 : LocalInstallationStore;
209
210 return yield* installationStore.load(context);
211 });197 });
212 198
213export const getSourceCredentialInteraction = (input: {199export const getSourceCredentialInteraction = (input: {
packages/platform/sdk/src/local/secrets.ts+12−72
@@ -16,22 +16,16 @@ import { · 15 unmodified lines
16 ControlPlaneNotFoundError,16 ControlPlaneNotFoundError,
17 ControlPlaneStorageError,17 ControlPlaneStorageError,
18} from "../errors";18} from "../errors";
19import { requireRuntimeLocalWorkspace } from "../runtime/local/runtime-context";19import { requireRuntimeLocalWorkspace } from "../runtime/workspace/runtime-context";
20import {20import {
21 createDefaultSecretMaterialDeleter,21 LocalInstanceConfigService,
22 createDefaultSecretMaterialStorer,22 SecretMaterialDeleterService,
23 createDefaultSecretMaterialUpdater,23 SecretMaterialStorerService,
24 ENV_SECRET_PROVIDER_ID,24 SecretMaterialUpdaterService,
25 KEYCHAIN_SECRET_PROVIDER_ID,25} from "../runtime/workspace/secret-material-providers";
26 LOCAL_SECRET_PROVIDER_ID,
27 parseSecretStoreProviderId,
28 resolveDefaultSecretStoreProviderId,
29} from "../runtime/local/secret-material-providers";
30import { RuntimeSourceStoreService } from "../runtime/sources/source-store";26import { RuntimeSourceStoreService } from "../runtime/sources/source-store";
31import { ControlPlaneStore } from "../runtime/store";27import { ControlPlaneStore } from "../runtime/store";
32 28
33const SECRET_STORE_PROVIDER_ENV = "EXECUTOR_SECRET_STORE_PROVIDER";
34
35const secretStorageError = (operation: string, message: string) =>29const secretStorageError = (operation: string, message: string) =>
36 new ControlPlaneStorageError({30 new ControlPlaneStorageError({
37 operation,31 operation,
@@ -39,45 +33,8 @@ const secretStorageError = (operation: string, message: string) => · 1 unmodified line
39 details: message,33 details: message,
40 });34 });
41 35
42export const getLocalInstanceConfig = (): Effect.Effect<InstanceConfig> => {36export const getLocalInstanceConfig = (): Effect.Effect<InstanceConfig, Error, LocalInstanceConfigService> =>
43 const explicitDefaultStoreProvider = parseSecretStoreProviderId(37 Effect.flatMap(LocalInstanceConfigService, (resolveInstanceConfig) => resolveInstanceConfig());
44 process.env[SECRET_STORE_PROVIDER_ENV],
45 );
46 const providers = [
47 {
48 id: LOCAL_SECRET_PROVIDER_ID,
49 name: "Local store",
50 canStore: true,
51 },
52 ];
53
54 if (process.platform === "darwin" || process.platform === "linux") {
55 providers.push({
56 id: KEYCHAIN_SECRET_PROVIDER_ID,
57 name:
58 process.platform === "darwin" ? "macOS Keychain" : "Desktop Keyring",
59 canStore:
60 process.platform === "darwin" ||
61 explicitDefaultStoreProvider === KEYCHAIN_SECRET_PROVIDER_ID,
62 });
63 }
64
65 providers.push({
66 id: ENV_SECRET_PROVIDER_ID,
67 name: "Environment variable",
68 canStore: false,
69 });
70
71 return resolveDefaultSecretStoreProviderId({
72 storeProviderId: explicitDefaultStoreProvider ?? undefined,
73 }).pipe(
74 Effect.map((resolvedDefaultStoreProvider) => ({
75 platform: process.platform,
76 secretProviders: providers,
77 defaultSecretStoreProvider: resolvedDefaultStoreProvider,
78 })),
79 );
80};
81 38
82export const listLocalSecrets = () =>39export const listLocalSecrets = () =>
83 Effect.gen(function* () {40 Effect.gen(function* () {
@@ -119,10 +76,6 @@ export const createLocalSecret = (payload: CreateSecretPayload) => · 35 unmodified lines
119 const name = payload.name.trim();76 const name = payload.name.trim();
120 const value = payload.value;77 const value = payload.value;
121 const purpose = payload.purpose ?? "auth_material";78 const purpose = payload.purpose ?? "auth_material";
122 const requestedProviderId =
123 payload.providerId === undefined
124 ? null
125 : parseSecretStoreProviderId(payload.providerId);
126 79
127 if (name.length === 0) {80 if (name.length === 0) {
128 return yield* new ControlPlaneBadRequestError({81 return yield* new ControlPlaneBadRequestError({
@@ -131,23 +84,14 @@ export const createLocalSecret = (payload: CreateSecretPayload) => · 2 unmodified lines
131 details: "Secret name is required.",84 details: "Secret name is required.",
132 });85 });
133 }86 }
134 if (payload.providerId !== undefined && requestedProviderId === null) {
135 return yield* new ControlPlaneBadRequestError({
136 operation: "secrets.create",
137 message: `Unsupported secret provider: ${payload.providerId}`,
138 details: `Unsupported secret provider: ${payload.providerId}`,
139 });
140 }
141 87
142 const store = yield* ControlPlaneStore;88 const store = yield* ControlPlaneStore;
143 const storeSecretMaterial = createDefaultSecretMaterialStorer({89 const storeSecretMaterial = yield* SecretMaterialStorerService;
144 rows: store,
145 ...(requestedProviderId ? { storeProviderId: requestedProviderId } : {}),
146 });
147 const ref = yield* storeSecretMaterial({90 const ref = yield* storeSecretMaterial({
148 name,91 name,
149 purpose,92 purpose,
150 value,93 value,
94 providerId: payload.providerId,
151 }).pipe(95 }).pipe(
152 Effect.mapError((cause) =>96 Effect.mapError((cause) =>
153 secretStorageError(97 secretStorageError(
@@ -214,9 +158,7 @@ export const updateLocalSecret = (input: { · 60 unmodified lines
214 update.name = input.payload.name.trim() || null;158 update.name = input.payload.name.trim() || null;
215 if (input.payload.value !== undefined) update.value = input.payload.value;159 if (input.payload.value !== undefined) update.value = input.payload.value;
216 160
217 const updateSecretMaterial = createDefaultSecretMaterialUpdater({161 const updateSecretMaterial = yield* SecretMaterialUpdaterService;
218 rows: store,
219 });
220 const updated = yield* updateSecretMaterial({162 const updated = yield* updateSecretMaterial({
221 ref: {163 ref: {
222 providerId: existing.value.providerId,164 providerId: existing.value.providerId,
@@ -260,9 +202,7 @@ export const deleteLocalSecret = (secretId: string) => · 37 unmodified lines
260 });202 });
261 }203 }
262 204
263 const deleteSecretMaterial = createDefaultSecretMaterialDeleter({205 const deleteSecretMaterial = yield* SecretMaterialDeleterService;
264 rows: store,
265 });
266 const removed = yield* deleteSecretMaterial({206 const removed = yield* deleteSecretMaterial({
267 providerId: existing.value.providerId,207 providerId: existing.value.providerId,
268 handle: existing.value.id,208 handle: existing.value.id,
packages/platform/sdk/src/policies/operations.ts+32−26
@@ -13,18 +13,17 @@ import { · 12 unmodified lines
13} from "../schema";13} from "../schema";
14import * as Effect from "effect/Effect";14import * as Effect from "effect/Effect";
15 15
16import { requireRuntimeLocalWorkspace } from "../runtime/local/runtime-context";16import { requireRuntimeLocalWorkspace } from "../runtime/workspace/runtime-context";
17import type {17import type {
18 WorkspaceConfigStoreShape,18 WorkspaceConfigStoreShape,
19 WorkspaceStateStoreShape,19 WorkspaceStateStoreShape,
20} from "../runtime/local/storage";20} from "../runtime/workspace/storage";
21import {21import {
22 WorkspaceConfigStore,22 WorkspaceConfigStore,
23 WorkspaceStateStore,23 WorkspaceStateStore,
24} from "../runtime/local/storage";24} from "../runtime/workspace/storage";
25import type { ResolvedLocalWorkspaceContext } from "../runtime/local/config";25import { type LocalWorkspaceState } from "../runtime/workspace-state";
26import { type LocalWorkspaceState } from "../runtime/local/workspace-state";26import { derivePolicyConfigKey } from "../runtime/workspace/workspace-sync";
27import { derivePolicyConfigKey } from "../runtime/local/workspace-sync";
28import {27import {
29 type OperationErrors,28 type OperationErrors,
30 operationErrors,29 operationErrors,
@@ -40,17 +39,22 @@ const policyOps = { · 9 unmodified lines
40 39
41const cloneJson = <T>(value: T): T => JSON.parse(JSON.stringify(value)) as T;40const cloneJson = <T>(value: T): T => JSON.parse(JSON.stringify(value)) as T;
42 41
42const workspacePolicyStableKey = (input: {
43 workspaceId: WorkspaceId;
44 workspaceRoot?: string | null;
45}): string => input.workspaceRoot?.trim() || input.workspaceId;
46
43const localPolicyIdForKey = (input: {47const localPolicyIdForKey = (input: {
44 workspaceRoot: string;48 workspaceStableKey: string;
45 key: string;49 key: string;
46}): PolicyId =>50}): PolicyId =>
47 PolicyIdSchema.make(51 PolicyIdSchema.make(
48 `pol_local_${createHash("sha256").update(`${input.workspaceRoot}:${input.key}`).digest("hex").slice(0, 16)}`,52 `pol_local_${createHash("sha256").update(`${input.workspaceStableKey}:${input.key}`).digest("hex").slice(0, 16)}`,
49 );53 );
50 54
51const toLocalWorkspacePolicy = (input: {55const toLocalWorkspacePolicy = (input: {
52 workspaceId: WorkspaceId;56 workspaceId: WorkspaceId;
53 workspaceRoot: string;57 workspaceStableKey: string;
54 key: string;58 key: string;
55 policyConfig: NonNullable<LocalExecutorConfig["policies"]>[string];59 policyConfig: NonNullable<LocalExecutorConfig["policies"]>[string];
56 state: LocalWorkspaceState["policies"][string] | undefined;60 state: LocalWorkspaceState["policies"][string] | undefined;
@@ -58,7 +62,7 @@ const toLocalWorkspacePolicy = (input: { · 1 unmodified line
58 id:62 id:
59 input.state?.id ??63 input.state?.id ??
60 localPolicyIdForKey({64 localPolicyIdForKey({
61 workspaceRoot: input.workspaceRoot,65 workspaceStableKey: input.workspaceStableKey,
62 key: input.key,66 key: input.key,
63 }),67 }),
64 key: input.key,68 key: input.key,
@@ -78,18 +82,17 @@ export const loadRuntimeLocalWorkspacePolicies = (workspaceId: WorkspaceId) => · 13 unmodified lines
78 yield* requireRuntimeLocalWorkspace(workspaceId);82 yield* requireRuntimeLocalWorkspace(workspaceId);
79 const workspaceConfigStore = yield* WorkspaceConfigStore;83 const workspaceConfigStore = yield* WorkspaceConfigStore;
80 const workspaceStateStore = yield* WorkspaceStateStore;84 const workspaceStateStore = yield* WorkspaceStateStore;
81 const loadedConfig = yield* workspaceConfigStore.load(85 const loadedConfig = yield* workspaceConfigStore.load();
82 runtimeLocalWorkspace.context,86 const workspaceState = yield* workspaceStateStore.load();
83 );
84 const workspaceState = yield* workspaceStateStore.load(
85 runtimeLocalWorkspace.context,
86 );
87 87
88 const policies = Object.entries(loadedConfig.config?.policies ?? {}).map(88 const policies = Object.entries(loadedConfig.config?.policies ?? {}).map(
89 ([key, policyConfig]) =>89 ([key, policyConfig]) =>
90 toLocalWorkspacePolicy({90 toLocalWorkspacePolicy({
91 workspaceId,91 workspaceId,
92 workspaceRoot: runtimeLocalWorkspace.context.workspaceRoot,92 workspaceStableKey: workspacePolicyStableKey({
93 workspaceId,
94 workspaceRoot: runtimeLocalWorkspace.workspace.workspaceRoot,
95 }),
93 key,96 key,
94 policyConfig,97 policyConfig,
95 state: workspaceState.policies[key],98 state: workspaceState.policies[key],
@@ -106,7 +109,6 @@ export const loadRuntimeLocalWorkspacePolicies = (workspaceId: WorkspaceId) => · 10 unmodified lines
106 109
107const writeLocalPolicyFiles = (input: {110const writeLocalPolicyFiles = (input: {
108 operation: OperationErrors;111 operation: OperationErrors;
109 context: ResolvedLocalWorkspaceContext;
110 workspaceConfigStore: WorkspaceConfigStoreShape;112 workspaceConfigStore: WorkspaceConfigStoreShape;
111 workspaceStateStore: WorkspaceStateStoreShape;113 workspaceStateStore: WorkspaceStateStoreShape;
112 projectConfig: LocalExecutorConfig;114 projectConfig: LocalExecutorConfig;
@@ -115,11 +117,9 @@ const writeLocalPolicyFiles = (input: { · 2 unmodified lines
115 Effect.all(117 Effect.all(
116 [118 [
117 input.workspaceConfigStore.writeProject({119 input.workspaceConfigStore.writeProject({
118 context: input.context,
119 config: input.projectConfig,120 config: input.projectConfig,
120 }),121 }),
121 input.workspaceStateStore.write({122 input.workspaceStateStore.write({
122 context: input.context,
123 state: input.workspaceState,123 state: input.workspaceState,
124 }),124 }),
125 ],125 ],
@@ -219,7 +219,10 @@ export const createPolicy = (input: { · 93 unmodified lines
219 id:219 id:
220 existingState?.id ??220 existingState?.id ??
221 localPolicyIdForKey({221 localPolicyIdForKey({
222 workspaceRoot: runtimeLocalWorkspace.context.workspaceRoot,222 workspaceStableKey: workspacePolicyStableKey({
223 workspaceId: input.workspaceId,
224 workspaceRoot: runtimeLocalWorkspace.workspace.workspaceRoot,
225 }),
223 key,226 key,
224 }),227 }),
225 createdAt: existingState?.createdAt ?? now,228 createdAt: existingState?.createdAt ?? now,
@@ -230,7 +233,6 @@ export const createPolicy = (input: { · 4 unmodified lines
230 233
231 yield* writeLocalPolicyFiles({234 yield* writeLocalPolicyFiles({
232 operation: policyOps.create,235 operation: policyOps.create,
233 context: runtimeLocalWorkspace.context,
234 workspaceConfigStore,236 workspaceConfigStore,
235 workspaceStateStore,237 workspaceStateStore,
236 projectConfig: {238 projectConfig: {
@@ -242,7 +244,10 @@ export const createPolicy = (input: { · 5 unmodified lines
242 244
243 return toLocalWorkspacePolicy({245 return toLocalWorkspacePolicy({
244 workspaceId: input.workspaceId,246 workspaceId: input.workspaceId,
245 workspaceRoot: runtimeLocalWorkspace.context.workspaceRoot,247 workspaceStableKey: workspacePolicyStableKey({
248 workspaceId: input.workspaceId,
249 workspaceRoot: runtimeLocalWorkspace.workspace.workspaceRoot,
250 }),
246 key,251 key,
247 policyConfig: policies[key]!,252 policyConfig: policies[key]!,
248 state: workspaceState.policies[key],253 state: workspaceState.policies[key],
@@ -355,7 +360,6 @@ export const updatePolicy = (input: { · 106 unmodified lines
355 360
356 yield* writeLocalPolicyFiles({361 yield* writeLocalPolicyFiles({
357 operation: policyOps.update,362 operation: policyOps.update,
358 context: runtimeLocalWorkspace.context,
359 workspaceConfigStore,363 workspaceConfigStore,
360 workspaceStateStore,364 workspaceStateStore,
361 projectConfig: {365 projectConfig: {
@@ -367,7 +371,10 @@ export const updatePolicy = (input: { · 5 unmodified lines
367 371
368 return toLocalWorkspacePolicy({372 return toLocalWorkspacePolicy({
369 workspaceId: input.workspaceId,373 workspaceId: input.workspaceId,
370 workspaceRoot: runtimeLocalWorkspace.context.workspaceRoot,374 workspaceStableKey: workspacePolicyStableKey({
375 workspaceId: input.workspaceId,
376 workspaceRoot: runtimeLocalWorkspace.workspace.workspaceRoot,
377 }),
371 key: existing.key,378 key: existing.key,
372 policyConfig: policies[existing.key]!,379 policyConfig: policies[existing.key]!,
373 state: workspaceState.policies[existing.key],380 state: workspaceState.policies[existing.key],
@@ -413,7 +420,6 @@ export const removePolicy = (input: { · 39 unmodified lines
413 localWorkspace.workspaceState.policies;420 localWorkspace.workspaceState.policies;
414 yield* writeLocalPolicyFiles({421 yield* writeLocalPolicyFiles({
415 operation: policyOps.remove,422 operation: policyOps.remove,
416 context: runtimeLocalWorkspace.context,
417 workspaceConfigStore,423 workspaceConfigStore,
418 workspaceStateStore,424 workspaceStateStore,
419 projectConfig: {425 projectConfig: {
packages/platform/sdk/src/runtime/__fixtures__/README.md+2−2
@@ -9,7 +9,7 @@ Each fixture directory should contain: · 8 unmodified lines
9- `.executor/state/workspace-state.json`: a trimmed workspace state with only the captured source9- `.executor/state/workspace-state.json`: a trimmed workspace state with only the captured source
10- `.executor/artifacts/sources/...`: the real artifact JSON and any sidecar files that release wrote10- `.executor/artifacts/sources/...`: the real artifact JSON and any sidecar files that release wrote
11 11
12`src/runtime/local/source-artifacts.test.ts` auto-discovers every fixture directory here that has a `fixture.json` file. Adding a new release fixture should not require changing the test.12`packages/platform/sdk-file/src/source-artifacts.test.ts` auto-discovers every fixture directory here that has a `fixture.json` file. Adding a new release fixture should not require changing the test.
13 13
14## Capture A New Release Fixture14## Capture A New Release Fixture
15 15
@@ -35,4 +35,4 @@ On releases where you want upgrade coverage: · 19 unmodified lines
351. Start from a real workspace that has a fully synced source on the released version.351. Start from a real workspace that has a fully synced source on the released version.
362. Run `bun run fixture:release:capture -- ...` for that source.362. Run `bun run fixture:release:capture -- ...` for that source.
373. Review the fixture for secrets before committing it.373. Review the fixture for secrets before committing it.
384. Run `bun run --cwd packages/platform/sdk test -- src/runtime/local/source-artifacts.test.ts`.384. Run `bun run --cwd packages/platform/sdk-file test -- src/source-artifacts.test.ts`.
packages/platform/sdk/src/runtime/auth/auth-artifacts.ts+1−1
@@ -35,7 +35,7 @@ import * as Schema from "effect/Schema"; · 34 unmodified lines
35import type {35import type {
36 ResolveSecretMaterial,36 ResolveSecretMaterial,
37 SecretMaterialResolveContext,37 SecretMaterialResolveContext,
38} from "../local/secret-material-providers";38} from "../workspace/secret-material-providers";
39import { runtimeEffectError } from "../effect-errors";39import { runtimeEffectError } from "../effect-errors";
40 40
41export type ResolvedSourceAuthMaterial = {41export type ResolvedSourceAuthMaterial = {
packages/platform/sdk/src/runtime/auth/auth-leases.ts+30−26
@@ -19,11 +19,11 @@ import * as Option from "effect/Option"; · 18 unmodified lines
19import type { ControlPlaneStoreShape } from "../store";19import type { ControlPlaneStoreShape } from "../store";
20import { resolveAuthArtifactMaterial, type ResolvedSourceAuthMaterial } from "./auth-artifacts";20import { resolveAuthArtifactMaterial, type ResolvedSourceAuthMaterial } from "./auth-artifacts";
21import {21import {
22 createDefaultSecretMaterialDeleter,22 type DeleteSecretMaterial,
23 createDefaultSecretMaterialStorer,
24 type ResolveSecretMaterial,23 type ResolveSecretMaterial,
25 type SecretMaterialResolveContext,24 type SecretMaterialResolveContext,
26} from "../local/secret-material-providers";25 type StoreSecretMaterial,
26} from "../workspace/secret-material-providers";
27import { refreshOAuth2AccessToken } from "./oauth2-pkce";27import { refreshOAuth2AccessToken } from "./oauth2-pkce";
28import type { OAuth2TokenResponse } from "./oauth2-pkce";28import type { OAuth2TokenResponse } from "./oauth2-pkce";
29import { createPersistedMcpAuthProvider } from "./mcp-auth-provider";29import { createPersistedMcpAuthProvider } from "./mcp-auth-provider";
@@ -43,13 +43,12 @@ const cleanupAuthLeaseSecretRefs = ( · 13 unmodified lines
43 previous: Pick<AuthLease, "placementsTemplateJson"> | null;43 previous: Pick<AuthLease, "placementsTemplateJson"> | null;
44 next: Pick<AuthLease, "placementsTemplateJson"> | null;44 next: Pick<AuthLease, "placementsTemplateJson"> | null;
45 },45 },
46 deleteSecretMaterial: DeleteSecretMaterial,
46) =>47) =>
47 Effect.gen(function* () {48 Effect.gen(function* () {
48 if (input.previous === null) {49 if (input.previous === null) {
49 return;50 return;
50 }51 }
51
52 const deleteSecretMaterial = createDefaultSecretMaterialDeleter({ rows });
53 const nextRefKeys = new Set(52 const nextRefKeys = new Set(
54 (input.next === null ? [] : authLeaseSecretRefs(input.next)).map(secretRefKey),53 (input.next === null ? [] : authLeaseSecretRefs(input.next)).map(secretRefKey),
55 );54 );
@@ -85,6 +84,8 @@ const refreshRefreshableOauth2AuthorizedUserArtifact = (input: { · 29 unmodified lines
85 artifact: AuthArtifact;84 artifact: AuthArtifact;
86 lease: AuthLease | null;85 lease: AuthLease | null;
87 resolveSecretMaterial: ResolveSecretMaterial;86 resolveSecretMaterial: ResolveSecretMaterial;
87 storeSecretMaterial: StoreSecretMaterial;
88 deleteSecretMaterial: DeleteSecretMaterial;
88 context?: SecretMaterialResolveContext;89 context?: SecretMaterialResolveContext;
89}): Effect.Effect<AuthLease, Error, never> =>90}): Effect.Effect<AuthLease, Error, never> =>
90 Effect.gen(function* () {91 Effect.gen(function* () {
@@ -116,10 +117,7 @@ const refreshRefreshableOauth2AuthorizedUserArtifact = (input: { · 25 unmodified lines
116 refreshToken,117 refreshToken,
117 });118 });
118 119
119 const storeSecretMaterial = createDefaultSecretMaterialStorer({120 const accessTokenRef = yield* input.storeSecretMaterial({
120 rows: input.rows,
121 });
122 const accessTokenRef = yield* storeSecretMaterial({
123 purpose: "oauth_access_token",121 purpose: "oauth_access_token",
124 value: tokenResponse.access_token,122 value: tokenResponse.access_token,
125 name: `${decoded.config.clientId} Access Token`,123 name: `${decoded.config.clientId} Access Token`,
@@ -166,7 +164,7 @@ const refreshRefreshableOauth2AuthorizedUserArtifact = (input: { · 40 unmodified lines
166 yield* cleanupAuthLeaseSecretRefs(input.rows, {164 yield* cleanupAuthLeaseSecretRefs(input.rows, {
167 previous: input.lease,165 previous: input.lease,
168 next: nextLease,166 next: nextLease,
169 });167 }, input.deleteSecretMaterial);
170 168
171 return nextLease;169 return nextLease;
172 });170 });
@@ -185,10 +183,10 @@ const workspaceOauthClientSecretRef = (input: { · 12 unmodified lines
185const cleanupProviderGrantRefreshToken = (rows: ControlPlaneStoreShape, input: {183const cleanupProviderGrantRefreshToken = (rows: ControlPlaneStoreShape, input: {
186 previous: SecretRef;184 previous: SecretRef;
187 next: SecretRef;185 next: SecretRef;
188}) =>186}, deleteSecretMaterial: DeleteSecretMaterial) =>
189 secretRefKey(input.previous) === secretRefKey(input.next)187 secretRefKey(input.previous) === secretRefKey(input.next)
190 ? Effect.void188 ? Effect.void
191 : createDefaultSecretMaterialDeleter({ rows })(input.previous).pipe(189 : deleteSecretMaterial(input.previous).pipe(
192 Effect.either,190 Effect.either,
193 Effect.ignore,191 Effect.ignore,
194 );192 );
@@ -198,6 +196,8 @@ const refreshProviderGrantRefArtifact = (input: { · 3 unmodified lines
198 artifact: AuthArtifact;196 artifact: AuthArtifact;
199 lease: AuthLease | null;197 lease: AuthLease | null;
200 resolveSecretMaterial: ResolveSecretMaterial;198 resolveSecretMaterial: ResolveSecretMaterial;
199 storeSecretMaterial: StoreSecretMaterial;
200 deleteSecretMaterial: DeleteSecretMaterial;
201 context?: SecretMaterialResolveContext;201 context?: SecretMaterialResolveContext;
202}): Effect.Effect<AuthLease, Error, never> =>202}): Effect.Effect<AuthLease, Error, never> =>
203 Effect.gen(function* () {203 Effect.gen(function* () {
@@ -239,17 +239,14 @@ const refreshProviderGrantRefArtifact = (input: { · 35 unmodified lines
239 scopes: config.requiredScopes.length > 0 ? config.requiredScopes : null,239 scopes: config.requiredScopes.length > 0 ? config.requiredScopes : null,
240 });240 });
241 241
242 const storeSecretMaterial = createDefaultSecretMaterialStorer({242 const accessTokenRef = yield* input.storeSecretMaterial({
243 rows: input.rows,
244 });
245 const accessTokenRef = yield* storeSecretMaterial({
246 purpose: "oauth_access_token",243 purpose: "oauth_access_token",
247 value: tokenResponse.access_token,244 value: tokenResponse.access_token,
248 name: `${grant.providerKey} Access Token`,245 name: `${grant.providerKey} Access Token`,
249 });246 });
250 247
251 const rotatedRefreshTokenRef = tokenResponse.refresh_token248 const rotatedRefreshTokenRef = tokenResponse.refresh_token
252 ? yield* storeSecretMaterial({249 ? yield* input.storeSecretMaterial({
253 purpose: "oauth_refresh_token",250 purpose: "oauth_refresh_token",
254 value: tokenResponse.refresh_token,251 value: tokenResponse.refresh_token,
255 name: `${grant.providerKey} Refresh Token`,252 name: `${grant.providerKey} Refresh Token`,
@@ -267,7 +264,7 @@ const refreshProviderGrantRefArtifact = (input: { · 11 unmodified lines
267 yield* cleanupProviderGrantRefreshToken(input.rows, {264 yield* cleanupProviderGrantRefreshToken(input.rows, {
268 previous: grant.refreshToken,265 previous: grant.refreshToken,
269 next: rotatedRefreshTokenRef,266 next: rotatedRefreshTokenRef,
270 });267 }, input.deleteSecretMaterial);
271 268
272 const expiresInMs =269 const expiresInMs =
273 typeof tokenResponse.expires_in === "number" && Number.isFinite(tokenResponse.expires_in)270 typeof tokenResponse.expires_in === "number" && Number.isFinite(tokenResponse.expires_in)
@@ -310,14 +307,14 @@ const refreshProviderGrantRefArtifact = (input: { · 36 unmodified lines
310 yield* cleanupAuthLeaseSecretRefs(input.rows, {307 yield* cleanupAuthLeaseSecretRefs(input.rows, {
311 previous: input.lease,308 previous: input.lease,
312 next: nextLease,309 next: nextLease,
313 });310 }, input.deleteSecretMaterial);
314 311
315 return nextLease;312 return nextLease;
316 });313 });
317 314
318export const removeAuthLeaseAndSecrets = (rows: ControlPlaneStoreShape, input: {315export const removeAuthLeaseAndSecrets = (rows: ControlPlaneStoreShape, input: {
319 authArtifactId: AuthArtifact["id"];316 authArtifactId: AuthArtifact["id"];
320}): Effect.Effect<void, Error, never> =>317}, deleteSecretMaterial: DeleteSecretMaterial): Effect.Effect<void, Error, never> =>
321 Effect.gen(function* () {318 Effect.gen(function* () {
322 const existingLease = yield* rows.authLeases.getByAuthArtifactId(input.authArtifactId);319 const existingLease = yield* rows.authLeases.getByAuthArtifactId(input.authArtifactId);
323 if (Option.isNone(existingLease)) {320 if (Option.isNone(existingLease)) {
@@ -328,13 +325,15 @@ export const removeAuthLeaseAndSecrets = (rows: ControlPlaneStoreShape, input: { · 4 unmodified lines
328 yield* cleanupAuthLeaseSecretRefs(rows, {325 yield* cleanupAuthLeaseSecretRefs(rows, {
329 previous: existingLease.value,326 previous: existingLease.value,
330 next: null,327 next: null,
331 });328 }, deleteSecretMaterial);
332 });329 });
333 330
334export const upsertOauth2AuthorizedUserLeaseFromTokenResponse = (input: {331export const upsertOauth2AuthorizedUserLeaseFromTokenResponse = (input: {
335 rows: ControlPlaneStoreShape;332 rows: ControlPlaneStoreShape;
336 artifact: AuthArtifact;333 artifact: AuthArtifact;
337 tokenResponse: OAuth2TokenResponse;334 tokenResponse: OAuth2TokenResponse;
335 storeSecretMaterial: StoreSecretMaterial;
336 deleteSecretMaterial: DeleteSecretMaterial;
338}): Effect.Effect<void, Error, never> =>337}): Effect.Effect<void, Error, never> =>
339 Effect.gen(function* () {338 Effect.gen(function* () {
340 const decoded = decodeBuiltInAuthArtifactConfig(input.artifact);339 const decoded = decodeBuiltInAuthArtifactConfig(input.artifact);
@@ -347,10 +346,7 @@ export const upsertOauth2AuthorizedUserLeaseFromTokenResponse = (input: { · 6 unmodified lines
347 346
348 const existingLeaseOption = yield* input.rows.authLeases.getByAuthArtifactId(input.artifact.id);347 const existingLeaseOption = yield* input.rows.authLeases.getByAuthArtifactId(input.artifact.id);
349 const existingLease = Option.isSome(existingLeaseOption) ? existingLeaseOption.value : null;348 const existingLease = Option.isSome(existingLeaseOption) ? existingLeaseOption.value : null;
350 const storeSecretMaterial = createDefaultSecretMaterialStorer({349 const accessTokenRef = yield* input.storeSecretMaterial({
351 rows: input.rows,
352 });
353 const accessTokenRef = yield* storeSecretMaterial({
354 purpose: "oauth_access_token",350 purpose: "oauth_access_token",
355 value: input.tokenResponse.access_token,351 value: input.tokenResponse.access_token,
356 name: `${decoded.config.clientId} Access Token`,352 name: `${decoded.config.clientId} Access Token`,
@@ -397,13 +393,15 @@ export const upsertOauth2AuthorizedUserLeaseFromTokenResponse = (input: { · 40 unmodified lines
397 yield* cleanupAuthLeaseSecretRefs(input.rows, {393 yield* cleanupAuthLeaseSecretRefs(input.rows, {
398 previous: existingLease,394 previous: existingLease,
399 next: nextLease,395 next: nextLease,
400 });396 }, input.deleteSecretMaterial);
401 });397 });
402 398
403export const resolveAuthArtifactMaterialWithLeases = (input: {399export const resolveAuthArtifactMaterialWithLeases = (input: {
404 rows: ControlPlaneStoreShape;400 rows: ControlPlaneStoreShape;
405 artifact: AuthArtifact | null;401 artifact: AuthArtifact | null;
406 resolveSecretMaterial: ResolveSecretMaterial;402 resolveSecretMaterial: ResolveSecretMaterial;
403 storeSecretMaterial: StoreSecretMaterial;
404 deleteSecretMaterial: DeleteSecretMaterial;
407 context?: SecretMaterialResolveContext;405 context?: SecretMaterialResolveContext;
408}): Effect.Effect<ResolvedSourceAuthMaterial, Error, never> =>406}): Effect.Effect<ResolvedSourceAuthMaterial, Error, never> =>
409 Effect.gen(function* () {407 Effect.gen(function* () {
@@ -432,6 +430,8 @@ export const resolveAuthArtifactMaterialWithLeases = (input: { · 22 unmodified lines
432 artifact: input.artifact,430 artifact: input.artifact,
433 lease: existingLease,431 lease: existingLease,
434 resolveSecretMaterial: input.resolveSecretMaterial,432 resolveSecretMaterial: input.resolveSecretMaterial,
433 storeSecretMaterial: input.storeSecretMaterial,
434 deleteSecretMaterial: input.deleteSecretMaterial,
435 context: input.context,435 context: input.context,
436 });436 });
437 437
@@ -451,6 +451,8 @@ export const resolveAuthArtifactMaterialWithLeases = (input: { · 13 unmodified lines
451 artifact: input.artifact,451 artifact: input.artifact,
452 lease: existingLease,452 lease: existingLease,
453 resolveSecretMaterial: input.resolveSecretMaterial,453 resolveSecretMaterial: input.resolveSecretMaterial,
454 storeSecretMaterial: input.storeSecretMaterial,
455 deleteSecretMaterial: input.deleteSecretMaterial,
454 context: input.context,456 context: input.context,
455 });457 });
456 458
@@ -476,6 +478,8 @@ export const resolveAuthArtifactMaterialWithLeases = (input: { · 19 unmodified lines
476 artifact: input.artifact,478 artifact: input.artifact,
477 config: mcpOAuthConfig,479 config: mcpOAuthConfig,
478 resolveSecretMaterial: input.resolveSecretMaterial,480 resolveSecretMaterial: input.resolveSecretMaterial,
481 storeSecretMaterial: input.storeSecretMaterial,
482 deleteSecretMaterial: input.deleteSecretMaterial,
479 context: input.context,483 context: input.context,
480 }),484 }),
481 };485 };
packages/platform/sdk/src/runtime/auth/mcp-auth-provider.ts+13−15
@@ -22,11 +22,11 @@ import * as Schema from "effect/Schema"; · 21 unmodified lines
22 22
23import type { ControlPlaneStoreShape } from "../store";23import type { ControlPlaneStoreShape } from "../store";
24import {24import {
25 createDefaultSecretMaterialDeleter,25 type DeleteSecretMaterial,
26 createDefaultSecretMaterialStorer,
27 type ResolveSecretMaterial,26 type ResolveSecretMaterial,
28 type SecretMaterialResolveContext,27 type SecretMaterialResolveContext,
29} from "../local/secret-material-providers";28 type StoreSecretMaterial,
29} from "../workspace/secret-material-providers";
30 30
31const encodeMcpOAuthAuthArtifactConfig = Schema.encodeSync(31const encodeMcpOAuthAuthArtifactConfig = Schema.encodeSync(
32 McpOAuthAuthArtifactConfigJsonSchema,32 McpOAuthAuthArtifactConfigJsonSchema,
@@ -62,24 +62,23 @@ const secretRefEquals = (left: SecretRef | null, right: SecretRef | null): boole · 29 unmodified lines
62 (left?.providerId ?? null) === (right?.providerId ?? null)62 (left?.providerId ?? null) === (right?.providerId ?? null)
63 && (left?.handle ?? null) === (right?.handle ?? null);63 && (left?.handle ?? null) === (right?.handle ?? null);
64 64
65const cleanupReplacedSecretRefs = (rows: ControlPlaneStoreShape, input: {65const cleanupReplacedSecretRefs = (input: {
66 deleteSecretMaterial: DeleteSecretMaterial;
66 previousAccessToken: SecretRef;67 previousAccessToken: SecretRef;
67 nextAccessToken: SecretRef;68 nextAccessToken: SecretRef;
68 previousRefreshToken: SecretRef | null;69 previousRefreshToken: SecretRef | null;
69 nextRefreshToken: SecretRef | null;70 nextRefreshToken: SecretRef | null;
70}) =>71}) =>
71 Effect.gen(function* () {72 Effect.gen(function* () {
72 const deleteSecretMaterial = createDefaultSecretMaterialDeleter({ rows });
73
74 if (!secretRefEquals(input.previousAccessToken, input.nextAccessToken)) {73 if (!secretRefEquals(input.previousAccessToken, input.nextAccessToken)) {
75 yield* Effect.either(deleteSecretMaterial(input.previousAccessToken));74 yield* Effect.either(input.deleteSecretMaterial(input.previousAccessToken));
76 }75 }
77 76
78 if (77 if (
79 input.previousRefreshToken !== null78 input.previousRefreshToken !== null
80 && !secretRefEquals(input.previousRefreshToken, input.nextRefreshToken)79 && !secretRefEquals(input.previousRefreshToken, input.nextRefreshToken)
81 ) {80 ) {
82 yield* Effect.either(deleteSecretMaterial(input.previousRefreshToken));81 yield* Effect.either(input.deleteSecretMaterial(input.previousRefreshToken));
83 }82 }
84 });83 });
85 84
@@ -115,12 +114,10 @@ export const createPersistedMcpAuthProvider = (input: { · 29 unmodified lines
115 artifact: AuthArtifact;114 artifact: AuthArtifact;
116 config: McpOAuthAuthArtifactConfig;115 config: McpOAuthAuthArtifactConfig;
117 resolveSecretMaterial: ResolveSecretMaterial;116 resolveSecretMaterial: ResolveSecretMaterial;
117 storeSecretMaterial: StoreSecretMaterial;
118 deleteSecretMaterial: DeleteSecretMaterial;
118 context?: SecretMaterialResolveContext;119 context?: SecretMaterialResolveContext;
119}): OAuthClientProvider => {120}): OAuthClientProvider => {
120 const storeSecretMaterial = createDefaultSecretMaterialStorer({
121 rows: input.rows,
122 });
123
124 let currentArtifact = input.artifact;121 let currentArtifact = input.artifact;
125 let currentConfig = input.config;122 let currentConfig = input.config;
126 123
@@ -186,13 +183,13 @@ export const createPersistedMcpAuthProvider = (input: { · 59 unmodified lines
186 saveTokens: (tokens) =>183 saveTokens: (tokens) =>
187 Effect.runPromise(Effect.gen(function* () {184 Effect.runPromise(Effect.gen(function* () {
188 const previousConfig = currentConfig;185 const previousConfig = currentConfig;
189 const accessTokenRef = yield* storeSecretMaterial({186 const accessTokenRef = yield* input.storeSecretMaterial({
190 purpose: "oauth_access_token",187 purpose: "oauth_access_token",
191 value: tokens.access_token,188 value: tokens.access_token,
192 name: `${currentArtifact.sourceId} MCP Access Token`,189 name: `${currentArtifact.sourceId} MCP Access Token`,
193 });190 });
194 const refreshTokenRef = tokens.refresh_token191 const refreshTokenRef = tokens.refresh_token
195 ? yield* storeSecretMaterial({192 ? yield* input.storeSecretMaterial({
196 purpose: "oauth_refresh_token",193 purpose: "oauth_refresh_token",
197 value: tokens.refresh_token,194 value: tokens.refresh_token,
198 name: `${currentArtifact.sourceId} MCP Refresh Token`,195 name: `${currentArtifact.sourceId} MCP Refresh Token`,
@@ -212,7 +209,8 @@ export const createPersistedMcpAuthProvider = (input: { · 13 unmodified lines
212 };209 };
213 210
214 yield* persistConfig(nextConfig);211 yield* persistConfig(nextConfig);
215 yield* cleanupReplacedSecretRefs(input.rows, {212 yield* cleanupReplacedSecretRefs({
213 deleteSecretMaterial: input.deleteSecretMaterial,
216 previousAccessToken: previousConfig.accessToken,214 previousAccessToken: previousConfig.accessToken,
217 nextAccessToken: accessTokenRef,215 nextAccessToken: accessTokenRef,
218 previousRefreshToken: previousConfig.refreshToken,216 previousRefreshToken: previousConfig.refreshToken,
packages/platform/sdk/src/runtime/auth/provider-grant-lifecycle.ts+4−5
@@ -9,7 +9,9 @@ import { · 8 unmodified lines
9import * as Effect from "effect/Effect";9import * as Effect from "effect/Effect";
10import * as Option from "effect/Option";10import * as Option from "effect/Option";
11 11
12import { createDefaultSecretMaterialDeleter } from "../local/secret-material-providers";12import {
13 type DeleteSecretMaterial,
14} from "../workspace/secret-material-providers";
13import type { ControlPlaneStoreShape } from "../store";15import type { ControlPlaneStoreShape } from "../store";
14 16
15const providerGrantRefFromArtifact = (17const providerGrantRefFromArtifact = (
@@ -81,11 +83,8 @@ export const markProviderGrantOrphanedIfUnused = (rows: ControlPlaneStoreShape, · 65 unmodified lines
81 83
82export const removeProviderAuthGrantSecret = (rows: ControlPlaneStoreShape, input: {84export const removeProviderAuthGrantSecret = (rows: ControlPlaneStoreShape, input: {
83 grant: ProviderAuthGrant;85 grant: ProviderAuthGrant;
84}): Effect.Effect<void, Error, never> =>86}, deleteSecretMaterial: DeleteSecretMaterial): Effect.Effect<void, Error, never> =>
85 Effect.gen(function* () {87 Effect.gen(function* () {
86 const deleteSecretMaterial = createDefaultSecretMaterialDeleter({
87 rows,
88 });
89 yield* deleteSecretMaterial(input.grant.refreshToken).pipe(88 yield* deleteSecretMaterial(input.grant.refreshToken).pipe(
90 Effect.either,89 Effect.either,
91 Effect.ignore,90 Effect.ignore,
packages/platform/sdk/src/runtime/auth/source-auth-material.ts+18−2
@@ -17,10 +17,16 @@ import { · 16 unmodified lines
17} from "./auth-artifacts";17} from "./auth-artifacts";
18import { resolveAuthArtifactMaterialWithLeases } from "./auth-leases";18import { resolveAuthArtifactMaterialWithLeases } from "./auth-leases";
19import type {19import type {
20 DeleteSecretMaterial,
20 ResolveSecretMaterial,21 ResolveSecretMaterial,
21 SecretMaterialResolveContext,22 SecretMaterialResolveContext,
22} from "../local/secret-material-providers";23 StoreSecretMaterial,
23import { SecretMaterialResolverService } from "../local/secret-material-providers";24} from "../workspace/secret-material-providers";
25import {
26 SecretMaterialDeleterService,
27 SecretMaterialResolverService,
28 SecretMaterialStorerService,
29} from "../workspace/secret-material-providers";
24import { runtimeEffectError } from "../effect-errors";30import { runtimeEffectError } from "../effect-errors";
25 31
26const authForSlot = (input: {32const authForSlot = (input: {
@@ -61,6 +67,8 @@ export const resolveSourceAuthMaterialWithDeps = (input: { · 34 unmodified lines
61 actorAccountId?: AccountId | null;67 actorAccountId?: AccountId | null;
62 rows?: ControlPlaneStoreShape;68 rows?: ControlPlaneStoreShape;
63 resolveSecretMaterial: ResolveSecretMaterial;69 resolveSecretMaterial: ResolveSecretMaterial;
70 storeSecretMaterial?: StoreSecretMaterial;
71 deleteSecretMaterial?: DeleteSecretMaterial;
64 context?: SecretMaterialResolveContext;72 context?: SecretMaterialResolveContext;
65}): Effect.Effect<ResolvedSourceAuthMaterial, Error, never> =>73}): Effect.Effect<ResolvedSourceAuthMaterial, Error, never> =>
66 Effect.gen(function* () {74 Effect.gen(function* () {
@@ -85,6 +93,8 @@ export const resolveSourceAuthMaterialWithDeps = (input: { · 18 unmodified lines
85 rows: input.rows,93 rows: input.rows,
86 artifact: artifactOption.value,94 artifact: artifactOption.value,
87 resolveSecretMaterial: input.resolveSecretMaterial,95 resolveSecretMaterial: input.resolveSecretMaterial,
96 storeSecretMaterial: input.storeSecretMaterial ?? (() => Effect.dieMessage("storeSecretMaterial unavailable")),
97 deleteSecretMaterial: input.deleteSecretMaterial ?? (() => Effect.dieMessage("deleteSecretMaterial unavailable")),
88 context: input.context,98 context: input.context,
89 });99 });
90 }100 }
@@ -106,6 +116,8 @@ export const resolveSourceAuthMaterialWithDeps = (input: { · 15 unmodified lines
106 rows: input.rows,116 rows: input.rows,
107 artifact,117 artifact,
108 resolveSecretMaterial: input.resolveSecretMaterial,118 resolveSecretMaterial: input.resolveSecretMaterial,
119 storeSecretMaterial: input.storeSecretMaterial ?? (() => Effect.dieMessage("storeSecretMaterial unavailable")),
120 deleteSecretMaterial: input.deleteSecretMaterial ?? (() => Effect.dieMessage("deleteSecretMaterial unavailable")),
109 context: input.context,121 context: input.context,
110 });122 });
111 }123 }
@@ -138,6 +150,8 @@ export const RuntimeSourceAuthMaterialLive = Layer.effect( · 26 unmodified lines
138 Effect.gen(function* () {150 Effect.gen(function* () {
139 const rows = yield* ControlPlaneStore;151 const rows = yield* ControlPlaneStore;
140 const resolveSecretMaterial = yield* SecretMaterialResolverService;152 const resolveSecretMaterial = yield* SecretMaterialResolverService;
153 const storeSecretMaterial = yield* SecretMaterialStorerService;
154 const deleteSecretMaterial = yield* SecretMaterialDeleterService;
141 155
142 return RuntimeSourceAuthMaterialService.of({156 return RuntimeSourceAuthMaterialService.of({
143 resolve: (input) =>157 resolve: (input) =>
@@ -145,6 +159,8 @@ export const RuntimeSourceAuthMaterialLive = Layer.effect( · 1 unmodified line
145 ...input,159 ...input,
146 rows,160 rows,
147 resolveSecretMaterial,161 resolveSecretMaterial,
162 storeSecretMaterial,
163 deleteSecretMaterial,
148 }),164 }),
149 });165 });
150 }),166 }),
packages/platform/sdk/src/runtime/catalog/source/reconcile.ts+2−3
@@ -5,8 +5,8 @@ import type { · 4 unmodified lines
5} from "#schema";5} from "#schema";
6import * as Effect from "effect/Effect";6import * as Effect from "effect/Effect";
7 7
8import { RuntimeLocalWorkspaceService } from "../../local/runtime-context";8import { RuntimeLocalWorkspaceService } from "../../workspace/runtime-context";
9import { SourceArtifactStore } from "../../local/storage";9import { SourceArtifactStore } from "../../workspace/storage";
10import { getSourceAdapterForSource } from "../../sources/source-adapters";10import { getSourceAdapterForSource } from "../../sources/source-adapters";
11import { RuntimeSourceStoreService } from "../../sources/source-store";11import { RuntimeSourceStoreService } from "../../sources/source-store";
12import { RuntimeSourceCatalogSyncService } from "./sync";12import { RuntimeSourceCatalogSyncService } from "./sync";
@@ -42,7 +42,6 @@ export const reconcileMissingSourceCatalogArtifacts = (input: { · 29 unmodified lines
42 }42 }
43 43
44 const artifact = yield* sourceArtifactStore.read({44 const artifact = yield* sourceArtifactStore.read({
45 context: runtimeLocalWorkspace.context,
46 sourceId: source.id,45 sourceId: source.id,
47 });46 });
48 if (artifact !== null) {47 if (artifact !== null) {
packages/platform/sdk/src/runtime/catalog/source/runtime.ts+11−20
@@ -27,7 +27,7 @@ import type { · 26 unmodified lines
27 Executable,27 Executable,
28 ShapeSymbol,28 ShapeSymbol,
29} from "@executor/ir/model";29} from "@executor/ir/model";
30import { LocalSourceArtifactMissingError } from "../../local/errors";30import { LocalSourceArtifactMissingError } from "../../workspace-errors";
31import {31import {
32 createCatalogTypeProjector,32 createCatalogTypeProjector,
33 documentationComment,33 documentationComment,
@@ -40,12 +40,12 @@ import { formatWithPrettier } from "../prettier-format"; · 6 unmodified lines
40import {40import {
41 RuntimeLocalWorkspaceService,41 RuntimeLocalWorkspaceService,
42 type RuntimeLocalWorkspaceState,42 type RuntimeLocalWorkspaceState,
43} from "../../local/runtime-context";43} from "../../workspace/runtime-context";
44import type { LocalSourceArtifact } from "../../local/source-artifacts";44import type { LocalSourceArtifact } from "../../source-artifacts";
45import {45import {
46 SourceArtifactStore,46 SourceArtifactStore,
47 type SourceArtifactStoreShape,47 type SourceArtifactStoreShape,
48} from "../../local/storage";48} from "../../workspace/storage";
49import {49import {
50 RuntimeSourceStoreService,50 RuntimeSourceStoreService,
51 type RuntimeSourceStore,51 type RuntimeSourceStore,
@@ -704,17 +704,16 @@ type SourceCatalogRuntimeServices = · 652 unmodified lines
704const ensureRuntimeCatalogWorkspace = (704const ensureRuntimeCatalogWorkspace = (
705 deps: RuntimeSourceCatalogStoreDeps,705 deps: RuntimeSourceCatalogStoreDeps,
706 workspaceId: WorkspaceId,706 workspaceId: WorkspaceId,
707) => {707) =>
708 Effect.gen(function* () {
708 if (deps.runtimeLocalWorkspace.installation.workspaceId !== workspaceId) {709 if (deps.runtimeLocalWorkspace.installation.workspaceId !== workspaceId) {
709 return Effect.fail(710 return yield* Effect.fail(
710 runtimeEffectError("catalog/source/runtime", 711 runtimeEffectError("catalog/source/runtime",
711 `Runtime local workspace mismatch: expected ${workspaceId}, got ${deps.runtimeLocalWorkspace.installation.workspaceId}`,712 `Runtime local workspace mismatch: expected ${workspaceId}, got ${deps.runtimeLocalWorkspace.installation.workspaceId}`,
712 ),713 ),
713 );714 );
714 }715 }
715 716 });
716 return Effect.succeed(deps.runtimeLocalWorkspace.context);
717};
718 717
719const buildSnapshotFromArtifact = (input: {718const buildSnapshotFromArtifact = (input: {
720 source: Source;719 source: Source;
@@ -728,10 +727,7 @@ const loadWorkspaceSourceCatalogsWithDeps = (deps: RuntimeSourceCatalogStoreDeps · 7 unmodified lines
728 actorAccountId?: AccountId | null;727 actorAccountId?: AccountId | null;
729}): Effect.Effect<readonly LoadedSourceCatalog[], Error, never> =>728}): Effect.Effect<readonly LoadedSourceCatalog[], Error, never> =>
730 Effect.gen(function* () {729 Effect.gen(function* () {
731 const workspaceContext = yield* ensureRuntimeCatalogWorkspace(730 yield* ensureRuntimeCatalogWorkspace(deps, input.workspaceId);
732 deps,
733 input.workspaceId,
734 );
735 const sources = yield* deps.sourceStore.loadSourcesInWorkspace(731 const sources = yield* deps.sourceStore.loadSourcesInWorkspace(
736 input.workspaceId,732 input.workspaceId,
737 {733 {
@@ -742,7 +738,6 @@ const loadWorkspaceSourceCatalogsWithDeps = (deps: RuntimeSourceCatalogStoreDeps · 4 unmodified lines
742 const localCatalogs = yield* Effect.forEach(sources, (source) =>738 const localCatalogs = yield* Effect.forEach(sources, (source) =>
743 Effect.gen(function* () {739 Effect.gen(function* () {
744 const artifact = yield* deps.sourceArtifactStore.read({740 const artifact = yield* deps.sourceArtifactStore.read({
745 context: workspaceContext,
746 sourceId: source.id,741 sourceId: source.id,
747 });742 });
748 if (artifact === null) {743 if (artifact === null) {
@@ -783,17 +778,13 @@ const loadSourceWithCatalogWithDeps = (deps: RuntimeSourceCatalogStoreDeps, inpu · 34 unmodified lines
783 actorAccountId?: AccountId | null;778 actorAccountId?: AccountId | null;
784}): Effect.Effect<LoadedSourceCatalog, Error | LocalSourceArtifactMissingError, never> =>779}): Effect.Effect<LoadedSourceCatalog, Error | LocalSourceArtifactMissingError, never> =>
785 Effect.gen(function* () {780 Effect.gen(function* () {
786 const workspaceContext = yield* ensureRuntimeCatalogWorkspace(781 yield* ensureRuntimeCatalogWorkspace(deps, input.workspaceId);
787 deps,
788 input.workspaceId,
789 );
790 const source = yield* deps.sourceStore.loadSourceById({782 const source = yield* deps.sourceStore.loadSourceById({
791 workspaceId: input.workspaceId,783 workspaceId: input.workspaceId,
792 sourceId: input.sourceId,784 sourceId: input.sourceId,
793 actorAccountId: input.actorAccountId,785 actorAccountId: input.actorAccountId,
794 });786 });
795 const artifact = yield* deps.sourceArtifactStore.read({787 const artifact = yield* deps.sourceArtifactStore.read({
796 context: workspaceContext,
797 sourceId: source.id,788 sourceId: source.id,
798 });789 });
799 if (artifact === null) {790 if (artifact === null) {
packages/platform/sdk/src/runtime/catalog/source/sync.ts+34−42
@@ -11,16 +11,16 @@ import * as Layer from "effect/Layer"; · 10 unmodified lines
11import {11import {
12 RuntimeLocalWorkspaceService,12 RuntimeLocalWorkspaceService,
13 type RuntimeLocalWorkspaceState,13 type RuntimeLocalWorkspaceState,
14} from "../../local/runtime-context";14} from "../../workspace/runtime-context";
15import {15import {
16 SourceArtifactStore,16 SourceArtifactStore,
17 type SourceArtifactStoreShape,17 type SourceArtifactStoreShape,
18 WorkspaceStateStore,18 WorkspaceStateStore,
19 type WorkspaceStateStoreShape,19 type WorkspaceStateStoreShape,
20} from "../../local/storage";20} from "../../workspace/storage";
21import {21import {
22 type LocalWorkspaceState,22 type LocalWorkspaceState,
23} from "../../local/workspace-state";23} from "../../workspace-state";
24import {24import {
25 RuntimeSourceAuthMaterialService,25 RuntimeSourceAuthMaterialService,
26} from "../../auth/source-auth-material";26} from "../../auth/source-auth-material";
@@ -30,10 +30,11 @@ import { · 3 unmodified lines
30import {30import {
31 catalogSyncResultFromMcpManifest,31 catalogSyncResultFromMcpManifest,
32} from "@executor/source-mcp";32} from "@executor/source-mcp";
33import { SecretMaterialResolverService } from "../../local/secret-material-providers";33import { SecretMaterialResolverService } from "../../workspace/secret-material-providers";
34import { snapshotFromSourceCatalogSyncResult } from "@executor/source-core";34import { snapshotFromSourceCatalogSyncResult } from "@executor/source-core";
35import {35import {
36 refreshSourceTypeDeclarationInBackground,36 SourceTypeDeclarationsRefresherService,
37 type SourceTypeDeclarationsRefresherShape,
37} from "./type-declarations";38} from "./type-declarations";
38import { runtimeEffectError } from "../../effect-errors";39import { runtimeEffectError } from "../../effect-errors";
39 40
@@ -46,6 +47,7 @@ type RuntimeSourceCatalogSyncDeps = { · 6 unmodified lines
46 runtimeLocalWorkspace: RuntimeLocalWorkspaceState;47 runtimeLocalWorkspace: RuntimeLocalWorkspaceState;
47 workspaceStateStore: WorkspaceStateStoreShape;48 workspaceStateStore: WorkspaceStateStoreShape;
48 sourceArtifactStore: SourceArtifactStoreShape;49 sourceArtifactStore: SourceArtifactStoreShape;
50 sourceTypeDeclarationsRefresher: SourceTypeDeclarationsRefresherShape;
49 resolveSecretMaterial: Effect.Effect.Success<typeof SecretMaterialResolverService>;51 resolveSecretMaterial: Effect.Effect.Success<typeof SecretMaterialResolverService>;
50 sourceAuthMaterialService: Effect.Effect.Success<typeof RuntimeSourceAuthMaterialService>;52 sourceAuthMaterialService: Effect.Effect.Success<typeof RuntimeSourceAuthMaterialService>;
51};53};
@@ -54,6 +56,7 @@ type SourceCatalogSyncServices = · 2 unmodified lines
54 | RuntimeLocalWorkspaceService56 | RuntimeLocalWorkspaceService
55 | WorkspaceStateStore57 | WorkspaceStateStore
56 | SourceArtifactStore58 | SourceArtifactStore
59 | SourceTypeDeclarationsRefresherService
57 | RuntimeSourceAuthMaterialService60 | RuntimeSourceAuthMaterialService
58 | SecretMaterialResolverService;61 | SecretMaterialResolverService;
59 62
@@ -75,17 +78,16 @@ export class RuntimeSourceCatalogSyncService extends Context.Tag( · 15 unmodified lines
75const ensureRuntimeCatalogSyncWorkspace = (78const ensureRuntimeCatalogSyncWorkspace = (
76 deps: RuntimeSourceCatalogSyncDeps,79 deps: RuntimeSourceCatalogSyncDeps,
77 workspaceId: Source["workspaceId"],80 workspaceId: Source["workspaceId"],
78) => {81) =>
82 Effect.gen(function* () {
79 if (deps.runtimeLocalWorkspace.installation.workspaceId !== workspaceId) {83 if (deps.runtimeLocalWorkspace.installation.workspaceId !== workspaceId) {
80 return Effect.fail(84 return yield* Effect.fail(
81 runtimeEffectError("catalog/source/sync", 85 runtimeEffectError("catalog/source/sync",
82 `Runtime local workspace mismatch: expected ${workspaceId}, got ${deps.runtimeLocalWorkspace.installation.workspaceId}`,86 `Runtime local workspace mismatch: expected ${workspaceId}, got ${deps.runtimeLocalWorkspace.installation.workspaceId}`,
83 ),87 ),
84 );88 );
85 }89 }
86 90 });
87 return Effect.succeed(deps.runtimeLocalWorkspace.context);
88};
89 91
90const syncSourceCatalogWithDeps = (92const syncSourceCatalogWithDeps = (
91 deps: RuntimeSourceCatalogSyncDeps,93 deps: RuntimeSourceCatalogSyncDeps,
@@ -95,13 +97,10 @@ const syncSourceCatalogWithDeps = ( · 3 unmodified lines
95 },97 },
96): Effect.Effect<void, Error, never> =>98): Effect.Effect<void, Error, never> =>
97 Effect.gen(function* () {99 Effect.gen(function* () {
98 const workspaceContext = yield* ensureRuntimeCatalogSyncWorkspace(100 yield* ensureRuntimeCatalogSyncWorkspace(deps, input.source.workspaceId);
99 deps,
100 input.source.workspaceId,
101 );
102 101
103 if (!shouldIndexSource(input.source)) {102 if (!shouldIndexSource(input.source)) {
104 const state = yield* deps.workspaceStateStore.load(workspaceContext);103 const state = yield* deps.workspaceStateStore.load();
105 const existingSourceState = state.sources[input.source.id];104 const existingSourceState = state.sources[input.source.id];
106 const nextState: LocalWorkspaceState = {105 const nextState: LocalWorkspaceState = {
107 ...state,106 ...state,
@@ -117,15 +116,11 @@ const syncSourceCatalogWithDeps = ( · 9 unmodified lines
117 },116 },
118 };117 };
119 yield* deps.workspaceStateStore.write({118 yield* deps.workspaceStateStore.write({
120 context: workspaceContext,
121 state: nextState,119 state: nextState,
122 });120 });
123 yield* Effect.sync(() => {121 yield* deps.sourceTypeDeclarationsRefresher.refreshSourceInBackground({
124 refreshSourceTypeDeclarationInBackground({122 source: input.source,
125 context: workspaceContext,123 snapshot: null,
126 source: input.source,
127 snapshot: null,
128 });
129 });124 });
130 return;125 return;
131 }126 }
@@ -143,7 +138,6 @@ const syncSourceCatalogWithDeps = ( · 11 unmodified lines
143 });138 });
144 const snapshot = snapshotFromSourceCatalogSyncResult(syncResult);139 const snapshot = snapshotFromSourceCatalogSyncResult(syncResult);
145 yield* deps.sourceArtifactStore.write({140 yield* deps.sourceArtifactStore.write({
146 context: workspaceContext,
147 sourceId: input.source.id,141 sourceId: input.source.id,
148 artifact: deps.sourceArtifactStore.build({142 artifact: deps.sourceArtifactStore.build({
149 source: input.source,143 source: input.source,
@@ -151,7 +145,7 @@ const syncSourceCatalogWithDeps = ( · 1 unmodified line
151 }),145 }),
152 });146 });
153 147
154 const state = yield* deps.workspaceStateStore.load(workspaceContext);148 const state = yield* deps.workspaceStateStore.load();
155 const existingSourceState = state.sources[input.source.id];149 const existingSourceState = state.sources[input.source.id];
156 const nextState: LocalWorkspaceState = {150 const nextState: LocalWorkspaceState = {
157 ...state,151 ...state,
@@ -167,16 +161,12 @@ const syncSourceCatalogWithDeps = ( · 9 unmodified lines
167 },161 },
168 };162 };
169 yield* deps.workspaceStateStore.write({163 yield* deps.workspaceStateStore.write({
170 context: workspaceContext,
171 state: nextState,164 state: nextState,
172 });165 });
173 166
174 yield* Effect.sync(() => {167 yield* deps.sourceTypeDeclarationsRefresher.refreshSourceInBackground({
175 refreshSourceTypeDeclarationInBackground({168 source: input.source,
176 context: workspaceContext,169 snapshot,
177 source: input.source,
178 snapshot,
179 });
180 });170 });
181 }).pipe(171 }).pipe(
182 Effect.withSpan("source.catalog.sync", {172 Effect.withSpan("source.catalog.sync", {
@@ -197,10 +187,7 @@ const persistMcpCatalogSnapshotFromManifestWithDeps = ( · 14 unmodified lines
197 },187 },
198): Effect.Effect<void, Error, never> =>188): Effect.Effect<void, Error, never> =>
199 Effect.gen(function* () {189 Effect.gen(function* () {
200 const workspaceContext = yield* ensureRuntimeCatalogSyncWorkspace(190 yield* ensureRuntimeCatalogSyncWorkspace(deps, input.source.workspaceId);
201 deps,
202 input.source.workspaceId,
203 );
204 const syncResult = catalogSyncResultFromMcpManifest({191 const syncResult = catalogSyncResultFromMcpManifest({
205 source: input.source,192 source: input.source,
206 endpoint: input.source.endpoint,193 endpoint: input.source.endpoint,
@@ -209,7 +196,6 @@ const persistMcpCatalogSnapshotFromManifestWithDeps = ( · 2 unmodified lines
209 const snapshot = snapshotFromSourceCatalogSyncResult(syncResult);196 const snapshot = snapshotFromSourceCatalogSyncResult(syncResult);
210 197
211 yield* deps.sourceArtifactStore.write({198 yield* deps.sourceArtifactStore.write({
212 context: workspaceContext,
213 sourceId: input.source.id,199 sourceId: input.source.id,
214 artifact: deps.sourceArtifactStore.build({200 artifact: deps.sourceArtifactStore.build({
215 source: input.source,201 source: input.source,
@@ -217,12 +203,9 @@ const persistMcpCatalogSnapshotFromManifestWithDeps = ( · 1 unmodified line
217 }),203 }),
218 });204 });
219 205
220 yield* Effect.sync(() => {206 yield* deps.sourceTypeDeclarationsRefresher.refreshSourceInBackground({
221 refreshSourceTypeDeclarationInBackground({207 source: input.source,
222 context: workspaceContext,208 snapshot,
223 source: input.source,
224 snapshot,
225 });
226 });209 });
227 });210 });
228 211
@@ -234,6 +217,8 @@ export const syncSourceCatalog = (input: { · 5 unmodified lines
234 const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService;217 const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService;
235 const workspaceStateStore = yield* WorkspaceStateStore;218 const workspaceStateStore = yield* WorkspaceStateStore;
236 const sourceArtifactStore = yield* SourceArtifactStore;219 const sourceArtifactStore = yield* SourceArtifactStore;
220 const sourceTypeDeclarationsRefresher =
221 yield* SourceTypeDeclarationsRefresherService;
237 const resolveSecretMaterial = yield* SecretMaterialResolverService;222 const resolveSecretMaterial = yield* SecretMaterialResolverService;
238 const sourceAuthMaterialService = yield* RuntimeSourceAuthMaterialService;223 const sourceAuthMaterialService = yield* RuntimeSourceAuthMaterialService;
239 224
@@ -242,6 +227,7 @@ export const syncSourceCatalog = (input: { · 2 unmodified lines
242 runtimeLocalWorkspace,227 runtimeLocalWorkspace,
243 workspaceStateStore,228 workspaceStateStore,
244 sourceArtifactStore,229 sourceArtifactStore,
230 sourceTypeDeclarationsRefresher,
245 resolveSecretMaterial,231 resolveSecretMaterial,
246 sourceAuthMaterialService,232 sourceAuthMaterialService,
247 },233 },
@@ -260,6 +246,8 @@ export const persistMcpCatalogSnapshotFromManifest = (input: { · 12 unmodified lines
260 const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService;246 const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService;
261 const workspaceStateStore = yield* WorkspaceStateStore;247 const workspaceStateStore = yield* WorkspaceStateStore;
262 const sourceArtifactStore = yield* SourceArtifactStore;248 const sourceArtifactStore = yield* SourceArtifactStore;
249 const sourceTypeDeclarationsRefresher =
250 yield* SourceTypeDeclarationsRefresherService;
263 const resolveSecretMaterial = yield* SecretMaterialResolverService;251 const resolveSecretMaterial = yield* SecretMaterialResolverService;
264 const sourceAuthMaterialService = yield* RuntimeSourceAuthMaterialService;252 const sourceAuthMaterialService = yield* RuntimeSourceAuthMaterialService;
265 253
@@ -268,6 +256,7 @@ export const persistMcpCatalogSnapshotFromManifest = (input: { · 2 unmodified lines
268 runtimeLocalWorkspace,256 runtimeLocalWorkspace,
269 workspaceStateStore,257 workspaceStateStore,
270 sourceArtifactStore,258 sourceArtifactStore,
259 sourceTypeDeclarationsRefresher,
271 resolveSecretMaterial,260 resolveSecretMaterial,
272 sourceAuthMaterialService,261 sourceAuthMaterialService,
273 },262 },
@@ -281,6 +270,8 @@ export const RuntimeSourceCatalogSyncLive = Layer.effect( · 7 unmodified lines
281 const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService;270 const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService;
282 const workspaceStateStore = yield* WorkspaceStateStore;271 const workspaceStateStore = yield* WorkspaceStateStore;
283 const sourceArtifactStore = yield* SourceArtifactStore;272 const sourceArtifactStore = yield* SourceArtifactStore;
273 const sourceTypeDeclarationsRefresher =
274 yield* SourceTypeDeclarationsRefresherService;
284 const resolveSecretMaterial = yield* SecretMaterialResolverService;275 const resolveSecretMaterial = yield* SecretMaterialResolverService;
285 const sourceAuthMaterialService = yield* RuntimeSourceAuthMaterialService;276 const sourceAuthMaterialService = yield* RuntimeSourceAuthMaterialService;
286 277
@@ -288,6 +279,7 @@ export const RuntimeSourceCatalogSyncLive = Layer.effect( · 1 unmodified line
288 runtimeLocalWorkspace,279 runtimeLocalWorkspace,
289 workspaceStateStore,280 workspaceStateStore,
290 sourceArtifactStore,281 sourceArtifactStore,
282 sourceTypeDeclarationsRefresher,
291 resolveSecretMaterial,283 resolveSecretMaterial,
292 sourceAuthMaterialService,284 sourceAuthMaterialService,
293 };285 };
packages/platform/sdk/src/runtime/catalog/source/type-declarations.ts+14−418
@@ -1,427 +1,23 @@
1import { join } from "node:path";
2import { FileSystem } from "@effect/platform";
3import { NodeFileSystem } from "@effect/platform-node";
4import type { Source } from "#schema";1import type { Source } from "#schema";
5import * as Cause from "effect/Cause";
6import * as Effect from "effect/Effect";
7import * as Fiber from "effect/Fiber";
8
9import { projectCatalogForAgentSdk } from "@executor/ir/catalog";
10import type { CatalogSnapshotV1 } from "@executor/ir/model";2import type { CatalogSnapshotV1 } from "@executor/ir/model";
11import {3import * as Context from "effect/Context";
12 createCatalogTypeProjector,4import * as Effect from "effect/Effect";
13 formatPropertyKey,
14 joinTypeNameSegments,
15 projectedCatalogTypeRoots,
16 shapeAllowsOmittedArgs,
17} from "../catalog-typescript";
18import type { ResolvedLocalWorkspaceContext } from "../../local/config";
19import {
20 LocalFileSystemError,
21 unknownLocalErrorDetails,
22} from "../../local/errors";
23 5
24type SourceDeclarationEntry = {6export type BoundSourceDeclarationEntry = {
25 source: Source;7 source: Source;
26 snapshot: CatalogSnapshotV1;8 snapshot: CatalogSnapshotV1;
27};9};
28 10
29type ToolMethodNode = {11export type SourceTypeDeclarationsRefresherShape = {
30 readonly segments: readonly string[];12 refreshWorkspaceInBackground: (input: {
31 readonly inputType: string;13 entries: readonly BoundSourceDeclarationEntry[];
32 readonly outputType: string;14 }) => Effect.Effect<void, never, never>;
33 readonly argsOptional: boolean;15 refreshSourceInBackground: (input: {
34};16 source: Source;
35 17 snapshot: CatalogSnapshotV1 | null;
36type SourceDeclarationModel = {18 }) => Effect.Effect<void, never, never>;
37 readonly methods: readonly ToolMethodNode[];
38 readonly supportingTypes: readonly string[];
39};
40
41type ToolTreeNode = {
42 method: ToolMethodNode | null;
43 children: Map<string, ToolTreeNode>;
44};
45
46type SourceDeclarationStub = {
47 sourceId: string;
48};
49
50const GENERATED_TYPES_DIRECTORY = "types";
51const GENERATED_SOURCE_TYPES_DIRECTORY = "sources";
52
53const mapFileSystemError = (path: string, action: string) => (cause: unknown) =>
54 new LocalFileSystemError({
55 message: `Failed to ${action} ${path}: ${unknownLocalErrorDetails(cause)}`,
56 action,
57 path,
58 details: unknownLocalErrorDetails(cause),
59 });
60
61const declarationDirectory = (context: ResolvedLocalWorkspaceContext): string =>
62 join(context.configDirectory, GENERATED_TYPES_DIRECTORY);
63
64const sourceDeclarationDirectory = (context: ResolvedLocalWorkspaceContext): string =>
65 join(declarationDirectory(context), GENERATED_SOURCE_TYPES_DIRECTORY);
66
67const sourceDeclarationFileName = (sourceId: string): string => `${sourceId}.d.ts`;
68
69const sourceDeclarationPath = (context: ResolvedLocalWorkspaceContext, sourceId: string): string =>
70 join(sourceDeclarationDirectory(context), sourceDeclarationFileName(sourceId));
71
72const aggregateDeclarationPath = (context: ResolvedLocalWorkspaceContext): string =>
73 join(declarationDirectory(context), "index.d.ts");
74
75const sourceTypeInterfaceName = (sourceId: string): string =>
76 `SourceTools_${sourceId.replace(/[^A-Za-z0-9_$]+/g, "_")}`;
77
78const methodSignature = (method: ToolMethodNode): string =>
79 `(${method.argsOptional ? "args?:" : "args:"} ${method.inputType}) => Promise<${method.outputType}>`;
80
81const createToolTreeNode = (): ToolTreeNode => ({
82 method: null,
83 children: new Map(),
84});
85
86const objectTypeLiteral = (
87 lines: readonly string[],
88 indent: string,
89): string => {
90 if (lines.length === 0) {
91 return "{}";
92 }
93
94 return [
95 "{",
96 ...lines.map((line) => `${indent}${line}`),
97 `${indent.slice(0, -2)}}`,
98 ].join("\n");
99};
100
101const renderToolTreeType = (node: ToolTreeNode, indentLevel: number): string => {
102 const indent = " ".repeat(indentLevel + 1);
103 const childLines = [...node.children.entries()]
104 .sort(([left], [right]) => left.localeCompare(right))
105 .map(([segment, child]) =>
106 `${formatPropertyKey(segment)}: ${renderToolTreeType(child, indentLevel + 1)};`
107 );
108
109 const objectType = objectTypeLiteral(childLines, indent);
110 if (node.method === null) {
111 return objectType;
112 }
113
114 const callable = methodSignature(node.method);
115 return node.children.size === 0
116 ? callable
117 : `(${callable}) & ${objectType}`;
118};
119
120const buildToolTree = (methods: readonly ToolMethodNode[]): ToolTreeNode => {
121 const root = createToolTreeNode();
122
123 for (const method of methods) {
124 let current = root;
125 for (const segment of method.segments) {
126 const existing = current.children.get(segment);
127 if (existing) {
128 current = existing;
129 continue;
130 }
131
132 const next = createToolTreeNode();
133 current.children.set(segment, next);
134 current = next;
135 }
136
137 current.method = method;
138 }
139
140 return root;
141};
142
143const buildSourceDeclarationModel = (snapshot: CatalogSnapshotV1): SourceDeclarationModel => {
144 const projected = projectCatalogForAgentSdk({
145 catalog: snapshot.catalog,
146 });
147 const toolDescriptors = Object.values(projected.toolDescriptors)
148 .sort((left, right) => left.toolPath.join(".").localeCompare(right.toolPath.join(".")));
149 const renderer = createCatalogTypeProjector({
150 catalog: projected.catalog,
151 roots: projectedCatalogTypeRoots(projected),
152 });
153
154 const methods = toolDescriptors
155 .map((descriptor) => ({
156 segments: descriptor.toolPath,
157 inputType: renderer.renderDeclarationShape(descriptor.callShapeId, {
158 aliasHint: joinTypeNameSegments(...descriptor.toolPath, "call"),
159 }),
160 outputType: descriptor.resultShapeId
161 ? renderer.renderDeclarationShape(descriptor.resultShapeId, {
162 aliasHint: joinTypeNameSegments(...descriptor.toolPath, "result"),
163 })
164 : "unknown",
165 argsOptional: shapeAllowsOmittedArgs(projected.catalog, descriptor.callShapeId),
166 }) satisfies ToolMethodNode);
167
168 return {
169 methods,
170 supportingTypes: renderer.supportingDeclarations(),
171 };
172};
173
174const sourceDeclarationText = (entry: SourceDeclarationEntry): string => {
175 const interfaceName = sourceTypeInterfaceName(entry.source.id);
176 const declarationModel = buildSourceDeclarationModel(entry.snapshot);
177 const tree = buildToolTree(declarationModel.methods);
178 const body = renderToolTreeType(tree, 0);
179
180 return [
181 "// Generated by executor. Do not edit by hand.",
182 `// Source: ${entry.source.name} (${entry.source.id})`,
183 "",
184 ...declarationModel.supportingTypes,
185 ...(declarationModel.supportingTypes.length > 0 ? [""] : []),
186 `export interface ${interfaceName} ${body}`,
187 "",
188 `export declare const tools: ${interfaceName};`,
189 `export type ${interfaceName}Tools = ${interfaceName};`,
190 "export default tools;",
191 "",
192 ].join("\n");
193};
194
195const aggregateDeclarationText = (entries: readonly SourceDeclarationEntry[]): string => {
196 return aggregateDeclarationTextFromSourceIds(
197 entries.map((entry) => ({ sourceId: entry.source.id })),
198 );
199};
200
201const aggregateDeclarationTextFromSourceIds = (entries: readonly SourceDeclarationStub[]): string => {
202 const sorted = [...entries].sort((left, right) => left.sourceId.localeCompare(right.sourceId));
203 const imports = sorted.map((entry) =>
204 `import type { ${sourceTypeInterfaceName(entry.sourceId)} } from "../sources/${entry.sourceId}";`
205 );
206 const intersections = sorted.map((entry) => sourceTypeInterfaceName(entry.sourceId));
207 const executorToolsType = intersections.length > 0
208 ? intersections.join(" & ")
209 : "{}";
210
211 return [
212 "// Generated by executor. Do not edit by hand.",
213 ...imports,
214 ...(imports.length > 0 ? [""] : []),
215 `export type ExecutorSourceTools = ${executorToolsType};`,
216 "",
217 "declare global {",
218 " const tools: ExecutorSourceTools;",
219 "}",
220 "",
221 "export declare const tools: ExecutorSourceTools;",
222 "export default tools;",
223 "",
224 ].join("\n");
225};
226
227export const syncWorkspaceSourceTypeDeclarations = (input: {
228 context: ResolvedLocalWorkspaceContext;
229 entries: readonly SourceDeclarationEntry[];
230}): Effect.Effect<void, LocalFileSystemError, FileSystem.FileSystem> =>
231 Effect.gen(function* () {
232 const fs = yield* FileSystem.FileSystem;
233 const declarationsDir = declarationDirectory(input.context);
234 const perSourceDir = sourceDeclarationDirectory(input.context);
235 const activeEntries = input.entries
236 .filter((entry) => entry.source.enabled && entry.source.status === "connected")
237 .sort((left, right) => left.source.id.localeCompare(right.source.id));
238
239 yield* fs.makeDirectory(declarationsDir, { recursive: true }).pipe(
240 Effect.mapError(mapFileSystemError(declarationsDir, "create declaration directory")),
241 );
242 yield* fs.makeDirectory(perSourceDir, { recursive: true }).pipe(
243 Effect.mapError(mapFileSystemError(perSourceDir, "create source declaration directory")),
244 );
245
246 const expectedFiles = new Set(
247 activeEntries.map((entry) => sourceDeclarationFileName(entry.source.id)),
248 );
249 const existingFiles = yield* fs.readDirectory(perSourceDir).pipe(
250 Effect.mapError(mapFileSystemError(perSourceDir, "read source declaration directory")),
251 );
252
253 for (const existingFile of existingFiles) {
254 if (expectedFiles.has(existingFile)) {
255 continue;
256 }
257
258 const stalePath = join(perSourceDir, existingFile);
259 yield* fs.remove(stalePath).pipe(
260 Effect.mapError(mapFileSystemError(stalePath, "remove stale source declaration")),
261 );
262 }
263
264 for (const entry of activeEntries) {
265 const filePath = sourceDeclarationPath(input.context, entry.source.id);
266 yield* fs.writeFileString(filePath, sourceDeclarationText(entry)).pipe(
267 Effect.mapError(mapFileSystemError(filePath, "write source declaration")),
268 );
269 }
270
271 const aggregatePath = aggregateDeclarationPath(input.context);
272 yield* fs.writeFileString(
273 aggregatePath,
274 aggregateDeclarationText(activeEntries),
275 ).pipe(
276 Effect.mapError(mapFileSystemError(aggregatePath, "write aggregate declaration")),
277 );
278 });
279
280export const syncSourceTypeDeclaration = (input: {
281 context: ResolvedLocalWorkspaceContext;
282 source: Source;
283 snapshot: CatalogSnapshotV1 | null;
284}): Effect.Effect<void, LocalFileSystemError, FileSystem.FileSystem> =>
285 Effect.gen(function* () {
286 const fs = yield* FileSystem.FileSystem;
287 const declarationsDir = declarationDirectory(input.context);
288 const perSourceDir = sourceDeclarationDirectory(input.context);
289
290 yield* fs.makeDirectory(declarationsDir, { recursive: true }).pipe(
291 Effect.mapError(mapFileSystemError(declarationsDir, "create declaration directory")),
292 );
293 yield* fs.makeDirectory(perSourceDir, { recursive: true }).pipe(
294 Effect.mapError(mapFileSystemError(perSourceDir, "create source declaration directory")),
295 );
296
297 const filePath = sourceDeclarationPath(input.context, input.source.id);
298 const shouldWrite = input.source.enabled
299 && input.source.status === "connected"
300 && input.snapshot !== null;
301
302 if (shouldWrite) {
303 const snapshot = input.snapshot;
304 if (snapshot === null) {
305 return;
306 }
307 yield* fs.writeFileString(
308 filePath,
309 sourceDeclarationText({
310 source: input.source,
311 snapshot,
312 }),
313 ).pipe(
314 Effect.mapError(mapFileSystemError(filePath, "write source declaration")),
315 );
316 } else {
317 const exists = yield* fs.exists(filePath).pipe(
318 Effect.mapError(mapFileSystemError(filePath, "check source declaration path")),
319 );
320 if (exists) {
321 yield* fs.remove(filePath).pipe(
322 Effect.mapError(mapFileSystemError(filePath, "remove source declaration")),
323 );
324 }
325 }
326
327 const sourceIds = (yield* fs.readDirectory(perSourceDir).pipe(
328 Effect.mapError(mapFileSystemError(perSourceDir, "read source declaration directory")),
329 ))
330 .filter((fileName) => fileName.endsWith(".d.ts"))
331 .map((fileName) => ({ sourceId: fileName.slice(0, -".d.ts".length) }));
332
333 const aggregatePath = aggregateDeclarationPath(input.context);
334 yield* fs.writeFileString(
335 aggregatePath,
336 aggregateDeclarationTextFromSourceIds(sourceIds),
337 ).pipe(
338 Effect.mapError(mapFileSystemError(aggregatePath, "write aggregate declaration")),
339 );
340 });
341
342export const syncWorkspaceSourceTypeDeclarationsNode = (input: {
343 context: ResolvedLocalWorkspaceContext;
344 entries: readonly SourceDeclarationEntry[];
345}): Effect.Effect<void, LocalFileSystemError, never> =>
346 syncWorkspaceSourceTypeDeclarations(input).pipe(Effect.provide(NodeFileSystem.layer));
347
348export const syncSourceTypeDeclarationNode = (input: {
349 context: ResolvedLocalWorkspaceContext;
350 source: Source;
351 snapshot: CatalogSnapshotV1 | null;
352}): Effect.Effect<void, LocalFileSystemError, never> =>
353 syncSourceTypeDeclaration(input).pipe(Effect.provide(NodeFileSystem.layer));
354
355const logBackgroundDeclarationError = (label: string, cause: unknown): void => {
356 const message = Cause.isCause(cause)
357 ? Cause.pretty(cause)
358 : cause instanceof Error
359 ? cause.message
360 : String(cause);
361 console.warn(`[source-types] ${label} failed: ${message}`);
362};
363
364const BACKGROUND_DECLARATION_REFRESH_DELAY = "1500 millis";
365const workspaceRefreshFibers = new Map<string, Fiber.RuntimeFiber<void, never>>();
366const sourceRefreshFibers = new Map<string, Fiber.RuntimeFiber<void, never>>();
367
368export const refreshWorkspaceSourceTypeDeclarationsInBackground = (input: {
369 context: ResolvedLocalWorkspaceContext;
370 entries: readonly SourceDeclarationEntry[];
371}): void => {
372 const key = input.context.configDirectory;
373 const existingFiber = workspaceRefreshFibers.get(key);
374 if (existingFiber) {
375 Effect.runFork(Fiber.interruptFork(existingFiber));
376 }
377
378 const fiber = Effect.runFork(
379 Effect.sleep(BACKGROUND_DECLARATION_REFRESH_DELAY).pipe(
380 Effect.zipRight(
381 syncWorkspaceSourceTypeDeclarationsNode(input).pipe(
382 Effect.catchAllCause((cause) =>
383 Effect.sync(() => {
384 logBackgroundDeclarationError("workspace declaration refresh", cause);
385 })
386 ),
387 ),
388 ),
389 ),
390 );
391
392 workspaceRefreshFibers.set(key, fiber);
393 fiber.addObserver(() => {
394 workspaceRefreshFibers.delete(key);
395 });
396};19};
397 20
398export const refreshSourceTypeDeclarationInBackground = (input: {21export class SourceTypeDeclarationsRefresherService extends Context.Tag(
399 context: ResolvedLocalWorkspaceContext;22 "#runtime/SourceTypeDeclarationsRefresherService",
400 source: Source;23)<SourceTypeDeclarationsRefresherService, SourceTypeDeclarationsRefresherShape>() {}
401 snapshot: CatalogSnapshotV1 | null;
402}): void => {
403 const key = `${input.context.configDirectory}:${input.source.id}`;
404 const existingFiber = sourceRefreshFibers.get(key);
405 if (existingFiber) {
406 Effect.runFork(Fiber.interruptFork(existingFiber));
407 }
408
409 const fiber = Effect.runFork(
410 Effect.sleep(BACKGROUND_DECLARATION_REFRESH_DELAY).pipe(
411 Effect.zipRight(
412 syncSourceTypeDeclarationNode(input).pipe(
413 Effect.catchAllCause((cause) =>
414 Effect.sync(() => {
415 logBackgroundDeclarationError(`source ${input.source.id} declaration refresh`, cause);
416 })
417 ),
418 ),
419 ),
420 ),
421 );
422
423 sourceRefreshFibers.set(key, fiber);
424 fiber.addObserver(() => {
425 sourceRefreshFibers.delete(key);
426 });
427};
packages/platform/sdk/src/runtime/control-plane-runtime.test.ts+9−11
@@ -31,28 +31,26 @@ import { · 30 unmodified lines
31 provideControlPlaneRuntime,31 provideControlPlaneRuntime,
32 RuntimeExecutionResolverService,32 RuntimeExecutionResolverService,
33} from "./index";33} from "./index";
34import { createLocalControlPlaneRuntime as createControlPlaneRuntime } from "../../../sdk-file/src/index";
35import { createSourceFromPayload } from "./sources/source-definitions";34import { createSourceFromPayload } from "./sources/source-definitions";
36import { decodeSourceCredentialSelectionContent } from "./sources/source-credential-interactions";35import { decodeSourceCredentialSelectionContent } from "./sources/source-credential-interactions";
37import { persistSource } from "./sources/source-store";36import { persistSource } from "./sources/source-store";
38import { withControlPlaneClient } from "./execution/test-http-client";37import { withControlPlaneClient } from "./execution/test-http-client";
39import { runtimeEffectError } from "./effect-errors";38import { runtimeEffectError } from "./effect-errors";
40import {39import {
40 buildLocalSourceArtifact,
41 createLocalControlPlaneRuntime as createControlPlaneRuntime,
42 deriveLocalInstallation,
43 readLocalSourceArtifact,
41 resolveLocalWorkspaceContext,44 resolveLocalWorkspaceContext,
45 writeLocalControlPlaneState,
46 writeLocalSourceArtifact,
42 writeProjectLocalExecutorConfig,47 writeProjectLocalExecutorConfig,
43} from "./local/config";48 writeLocalWorkspaceState,
44import { writeLocalControlPlaneState } from "./local/control-plane-store";49} from "../../../sdk-file/src/index";
45import { deriveLocalInstallation } from "./local/installation";
46import {50import {
47 decodeSourceCredentialInteraction,51 decodeSourceCredentialInteraction,
48 submitSourceCredentialInteraction,52 submitSourceCredentialInteraction,
49} from "./local/operations";53} from "../local/operations";
50import {
51 buildLocalSourceArtifact,
52 readLocalSourceArtifact,
53 writeLocalSourceArtifact,
54} from "./local/source-artifacts";
55import { writeLocalWorkspaceState } from "./local/workspace-state";
56 54
57const makeRuntime = Effect.gen(function* () {55const makeRuntime = Effect.gen(function* () {
58 const fs = yield* FileSystem.FileSystem;56 const fs = yield* FileSystem.FileSystem;
packages/platform/sdk/src/runtime/execution/service.ts+1−1
@@ -38,7 +38,7 @@ import { · 37 unmodified lines
38import {38import {
39 getRuntimeLocalWorkspaceOption,39 getRuntimeLocalWorkspaceOption,
40 provideOptionalRuntimeLocalWorkspace,40 provideOptionalRuntimeLocalWorkspace,
41} from "../local/runtime-context";41} from "../workspace/runtime-context";
42import {42import {
43 asOperationErrors,43 asOperationErrors,
44 operationErrors,44 operationErrors,
packages/platform/sdk/src/runtime/execution/workspace/authorization.ts+13−11
@@ -5,12 +5,11 @@ import * as Effect from "effect/Effect"; · 4 unmodified lines
5import * as Schema from "effect/Schema";5import * as Schema from "effect/Schema";
6 6
7import type { LoadedSourceCatalogToolIndexEntry } from "../../catalog/source/runtime";7import type { LoadedSourceCatalogToolIndexEntry } from "../../catalog/source/runtime";
8import type { SecretMaterialResolveContext } from "../../local/secret-material-providers";8import type { SecretMaterialResolveContext } from "../../workspace/secret-material-providers";
9import type { WorkspaceStorageServices } from "../../local/storage";9import type { WorkspaceStorageServices } from "../../workspace/storage";
10import { invocationDescriptorFromTool } from "../ir-execution";10import { invocationDescriptorFromTool } from "../ir-execution";
11import { evaluateInvocationPolicy } from "../../policy/invocation-policy-engine";11import { evaluateInvocationPolicy } from "../../policy/invocation-policy-engine";
12import { loadRuntimeLocalWorkspacePolicies } from "../../policy/policies-operations";12import { loadRuntimeLocalWorkspacePolicies } from "../../../policies/operations";
13import { runtimeEffectError } from "../../effect-errors";
14 13
15const asToolPath = (value: string): ToolPath => value as ToolPath;14const asToolPath = (value: string): ToolPath => value as ToolPath;
16 15
@@ -62,6 +61,9 @@ export const toSecretResolutionContext = ( · 45 unmodified lines
62 };61 };
63};62};
64 63
64const failAuthorization = (message: string): Effect.Effect<never, Error, never> =>
65 Effect.fail(new Error(message));
66
65type WorkspaceToolElicitation = Parameters<67type WorkspaceToolElicitation = Parameters<
66 typeof makeToolInvokerFromTools68 typeof makeToolInvokerFromTools
67>[0]["onElicitation"];69>[0]["onElicitation"];
@@ -99,13 +101,13 @@ export const authorizePersistedToolInvocation = (input: { · 31 unmodified lines
99 }101 }
100 102
101 if (decision.kind === "deny") {103 if (decision.kind === "deny") {
102 return yield* runtimeEffectError("execution/workspace/authorization", decision.reason);104 return yield* failAuthorization(decision.reason);
103 }105 }
104 106
105 if (!input.onElicitation) {107 if (!input.onElicitation) {
106 return yield* runtimeEffectError("execution/workspace/authorization", 108 return yield* failAuthorization(
107 `Approval required for ${descriptor.toolPath}, but no elicitation-capable host is available`,109 `Approval required for ${descriptor.toolPath}, but no elicitation-capable host is available`,
108 );110 );
109 }111 }
110 112
111 const interactionId =113 const interactionId =
@@ -144,8 +146,8 @@ export const authorizePersistedToolInvocation = (input: { · 32 unmodified lines
144 );146 );
145 147
146 if (response.action !== "accept") {148 if (response.action !== "accept") {
147 return yield* runtimeEffectError("execution/workspace/authorization", 149 return yield* failAuthorization(
148 `Tool invocation not approved for ${descriptor.toolPath}`,150 `Tool invocation not approved for ${descriptor.toolPath}`,
149 );151 );
150 }152 }
151 });153 });
packages/platform/sdk/src/runtime/execution/workspace/environment.ts+27−9
@@ -19,12 +19,18 @@ import { RuntimeSourceAuthServiceTag } from "../../sources/source-auth-service"; · 18 unmodified lines
19import { RuntimeSourceCatalogStoreService } from "../../catalog/source/runtime";19import { RuntimeSourceCatalogStoreService } from "../../catalog/source/runtime";
20import { RuntimeSourceAuthMaterialService } from "../../auth/source-auth-material";20import { RuntimeSourceAuthMaterialService } from "../../auth/source-auth-material";
21import { RuntimeSourceCatalogSyncService } from "../../catalog/source/sync";21import { RuntimeSourceCatalogSyncService } from "../../catalog/source/sync";
22import { getRuntimeLocalWorkspaceOption } from "../../local/runtime-context";22import { getRuntimeLocalWorkspaceOption } from "../../workspace/runtime-context";
23import {
24 LocalInstanceConfigService,
25 SecretMaterialDeleterService,
26 SecretMaterialStorerService,
27 SecretMaterialUpdaterService,
28} from "../../workspace/secret-material-providers";
23import {29import {
24 LocalToolRuntimeLoaderService,30 LocalToolRuntimeLoaderService,
25 type LocalToolRuntimeLoaderShape,31 type LocalToolRuntimeLoaderShape,
26 type LocalToolRuntime,32 type LocalToolRuntime,
27} from "../../local/tools";33} from "../../local-tool-runtime";
28import {34import {
29 InstallationStore,35 InstallationStore,
30 type InstallationStoreShape,36 type InstallationStoreShape,
@@ -34,7 +40,7 @@ import { · 3 unmodified lines
34 type WorkspaceConfigStoreShape,40 type WorkspaceConfigStoreShape,
35 WorkspaceStateStore,41 WorkspaceStateStore,
36 type WorkspaceStateStoreShape,42 type WorkspaceStateStoreShape,
37} from "../../local/storage";43} from "../../workspace/storage";
38import { ControlPlaneStore } from "../../store";44import { ControlPlaneStore } from "../../store";
39import { RuntimeSourceStoreService } from "../../sources/source-store";45import { RuntimeSourceStoreService } from "../../sources/source-store";
40import type { CreateWorkspaceInternalToolMap } from "./tool-invoker";46import type { CreateWorkspaceInternalToolMap } from "./tool-invoker";
@@ -68,6 +74,10 @@ export const createWorkspaceExecutionEnvironmentResolver = · 27 unmodified lines
68 >;74 >;
69 localToolRuntimeLoader: LocalToolRuntimeLoaderShape;75 localToolRuntimeLoader: LocalToolRuntimeLoaderShape;
70 installationStore: InstallationStoreShape;76 installationStore: InstallationStoreShape;
77 instanceConfigResolver: Effect.Effect.Success<typeof LocalInstanceConfigService>;
78 storeSecretMaterial: Effect.Effect.Success<typeof SecretMaterialStorerService>;
79 deleteSecretMaterial: Effect.Effect.Success<typeof SecretMaterialDeleterService>;
80 updateSecretMaterial: Effect.Effect.Success<typeof SecretMaterialUpdaterService>;
71 workspaceConfigStore: WorkspaceConfigStoreShape;81 workspaceConfigStore: WorkspaceConfigStoreShape;
72 workspaceStateStore: WorkspaceStateStoreShape;82 workspaceStateStore: WorkspaceStateStoreShape;
73 sourceArtifactStore: SourceArtifactStoreShape;83 sourceArtifactStore: SourceArtifactStoreShape;
@@ -79,15 +89,11 @@ export const createWorkspaceExecutionEnvironmentResolver = · 5 unmodified lines
79 const loadedConfig =89 const loadedConfig =
80 runtimeLocalWorkspace === null90 runtimeLocalWorkspace === null
81 ? null91 ? null
82 : yield* input.workspaceConfigStore.load(92 : yield* input.workspaceConfigStore.load();
83 runtimeLocalWorkspace.context,
84 );
85 const localToolRuntime =93 const localToolRuntime =
86 runtimeLocalWorkspace === null94 runtimeLocalWorkspace === null
87 ? createEmptyLocalToolRuntime()95 ? createEmptyLocalToolRuntime()
88 : yield* input.localToolRuntimeLoader.load(96 : yield* input.localToolRuntimeLoader.load();
89 runtimeLocalWorkspace.context,
90 );
91 const { catalog, toolInvoker } = createWorkspaceToolInvoker({97 const { catalog, toolInvoker } = createWorkspaceToolInvoker({
92 workspaceId,98 workspaceId,
93 accountId,99 accountId,
@@ -96,6 +102,10 @@ export const createWorkspaceExecutionEnvironmentResolver = · 2 unmodified lines
96 sourceCatalogSyncService: input.sourceCatalogSyncService,102 sourceCatalogSyncService: input.sourceCatalogSyncService,
97 sourceCatalogStore: input.sourceCatalogStore,103 sourceCatalogStore: input.sourceCatalogStore,
98 installationStore: input.installationStore,104 installationStore: input.installationStore,
105 instanceConfigResolver: input.instanceConfigResolver,
106 storeSecretMaterial: input.storeSecretMaterial,
107 deleteSecretMaterial: input.deleteSecretMaterial,
108 updateSecretMaterial: input.updateSecretMaterial,
99 workspaceConfigStore: input.workspaceConfigStore,109 workspaceConfigStore: input.workspaceConfigStore,
100 workspaceStateStore: input.workspaceStateStore,110 workspaceStateStore: input.workspaceStateStore,
101 sourceArtifactStore: input.sourceArtifactStore,111 sourceArtifactStore: input.sourceArtifactStore,
@@ -146,6 +156,10 @@ export const RuntimeExecutionResolverLive = ( · 44 unmodified lines
146 const sourceCatalogStore = yield* RuntimeSourceCatalogStoreService;156 const sourceCatalogStore = yield* RuntimeSourceCatalogStoreService;
147 const localToolRuntimeLoader = yield* LocalToolRuntimeLoaderService;157 const localToolRuntimeLoader = yield* LocalToolRuntimeLoaderService;
148 const installationStore = yield* InstallationStore;158 const installationStore = yield* InstallationStore;
159 const instanceConfigResolver = yield* LocalInstanceConfigService;
160 const storeSecretMaterial = yield* SecretMaterialStorerService;
161 const deleteSecretMaterial = yield* SecretMaterialDeleterService;
162 const updateSecretMaterial = yield* SecretMaterialUpdaterService;
149 const workspaceConfigStore = yield* WorkspaceConfigStore;163 const workspaceConfigStore = yield* WorkspaceConfigStore;
150 const workspaceStateStore = yield* WorkspaceStateStore;164 const workspaceStateStore = yield* WorkspaceStateStore;
151 const sourceArtifactStore = yield* SourceArtifactStore;165 const sourceArtifactStore = yield* SourceArtifactStore;
@@ -159,6 +173,10 @@ export const RuntimeExecutionResolverLive = ( · 7 unmodified lines
159 sourceCatalogStore,173 sourceCatalogStore,
160 localToolRuntimeLoader,174 localToolRuntimeLoader,
161 installationStore,175 installationStore,
176 instanceConfigResolver,
177 storeSecretMaterial,
178 deleteSecretMaterial,
179 updateSecretMaterial,
162 workspaceConfigStore,180 workspaceConfigStore,
163 workspaceStateStore,181 workspaceStateStore,
164 sourceArtifactStore,182 sourceArtifactStore,
packages/platform/sdk/src/runtime/execution/workspace/local.ts+1−1
@@ -3,7 +3,7 @@ import * as Effect from "effect/Effect"; · 2 unmodified lines
3import {3import {
4 provideOptionalRuntimeLocalWorkspace,4 provideOptionalRuntimeLocalWorkspace,
5 type RuntimeLocalWorkspaceState,5 type RuntimeLocalWorkspaceState,
6} from "../../local/runtime-context";6} from "../../workspace/runtime-context";
7 7
8export const provideRuntimeLocalWorkspace = <A, E, R>(8export const provideRuntimeLocalWorkspace = <A, E, R>(
9 effect: Effect.Effect<A, E, R>,9 effect: Effect.Effect<A, E, R>,
packages/platform/sdk/src/runtime/execution/workspace/source-catalog.ts+2−2
@@ -10,14 +10,14 @@ import { · 9 unmodified lines
10 type LoadedSourceCatalogToolIndexEntry,10 type LoadedSourceCatalogToolIndexEntry,
11 catalogToolCatalogEntry,11 catalogToolCatalogEntry,
12} from "../../catalog/source/runtime";12} from "../../catalog/source/runtime";
13import type { RuntimeLocalWorkspaceState } from "../../local/runtime-context";13import type { RuntimeLocalWorkspaceState } from "../../workspace/runtime-context";
14import {14import {
15 makeWorkspaceStorageLayer,15 makeWorkspaceStorageLayer,
16 type SourceArtifactStoreShape,16 type SourceArtifactStoreShape,
17 type WorkspaceConfigStoreShape,17 type WorkspaceConfigStoreShape,
18 type WorkspaceStateStoreShape,18 type WorkspaceStateStoreShape,
19 type WorkspaceStorageServices,19 type WorkspaceStorageServices,
20} from "../../local/storage";20} from "../../workspace/storage";
21import { provideRuntimeLocalWorkspace } from "./local";21import { provideRuntimeLocalWorkspace } from "./local";
22 22
23const tokenize = (value: string): string[] =>23const tokenize = (value: string): string[] =>
packages/platform/sdk/src/runtime/execution/workspace/tool-invoker.ts+21−3
@@ -13,15 +13,21 @@ import * as Effect from "effect/Effect"; · 12 unmodified lines
13 13
14import { RuntimeSourceAuthMaterialService } from "../../auth/source-auth-material";14import { RuntimeSourceAuthMaterialService } from "../../auth/source-auth-material";
15import { RuntimeSourceCatalogStoreService } from "../../catalog/source/runtime";15import { RuntimeSourceCatalogStoreService } from "../../catalog/source/runtime";
16import type { RuntimeLocalWorkspaceState } from "../../local/runtime-context";16import type { RuntimeLocalWorkspaceState } from "../../workspace/runtime-context";
17import { type LocalToolRuntime } from "../../local/tools";17import { type LocalToolRuntime } from "../../local-tool-runtime";
18import {18import {
19 type InstallationStoreShape,19 type InstallationStoreShape,
20 makeWorkspaceStorageLayer,20 makeWorkspaceStorageLayer,
21 type SourceArtifactStoreShape,21 type SourceArtifactStoreShape,
22 type WorkspaceConfigStoreShape,22 type WorkspaceConfigStoreShape,
23 type WorkspaceStateStoreShape,23 type WorkspaceStateStoreShape,
24} from "../../local/storage";24} from "../../workspace/storage";
25import type {
26 DeleteSecretMaterial,
27 ResolveInstanceConfig,
28 StoreSecretMaterial,
29 UpdateSecretMaterial,
30} from "../../workspace/secret-material-providers";
25import type { ControlPlaneStoreShape } from "../../store";31import type { ControlPlaneStoreShape } from "../../store";
26import { RuntimeSourceAuthService } from "../../sources/source-auth-service";32import { RuntimeSourceAuthService } from "../../sources/source-auth-service";
27import { type RuntimeSourceStore } from "../../sources/source-store";33import { type RuntimeSourceStore } from "../../sources/source-store";
@@ -49,6 +55,10 @@ export type WorkspaceInternalToolContext = { · 21 unmodified lines
49 >;55 >;
50 sourceAuthService: RuntimeSourceAuthService;56 sourceAuthService: RuntimeSourceAuthService;
51 installationStore: InstallationStoreShape;57 installationStore: InstallationStoreShape;
58 instanceConfigResolver: ResolveInstanceConfig;
59 storeSecretMaterial: StoreSecretMaterial;
60 deleteSecretMaterial: DeleteSecretMaterial;
61 updateSecretMaterial: UpdateSecretMaterial;
52 workspaceConfigStore: WorkspaceConfigStoreShape;62 workspaceConfigStore: WorkspaceConfigStoreShape;
53 workspaceStateStore: WorkspaceStateStoreShape;63 workspaceStateStore: WorkspaceStateStoreShape;
54 sourceArtifactStore: SourceArtifactStoreShape;64 sourceArtifactStore: SourceArtifactStoreShape;
@@ -71,6 +81,10 @@ export const createWorkspaceToolInvoker = (input: { · 16 unmodified lines
71 typeof RuntimeSourceCatalogStoreService81 typeof RuntimeSourceCatalogStoreService
72 >;82 >;
73 installationStore: InstallationStoreShape;83 installationStore: InstallationStoreShape;
84 instanceConfigResolver: ResolveInstanceConfig;
85 storeSecretMaterial: StoreSecretMaterial;
86 deleteSecretMaterial: DeleteSecretMaterial;
87 updateSecretMaterial: UpdateSecretMaterial;
74 workspaceConfigStore: WorkspaceConfigStoreShape;88 workspaceConfigStore: WorkspaceConfigStoreShape;
75 workspaceStateStore: WorkspaceStateStoreShape;89 workspaceStateStore: WorkspaceStateStoreShape;
76 sourceArtifactStore: SourceArtifactStoreShape;90 sourceArtifactStore: SourceArtifactStoreShape;
@@ -115,6 +129,10 @@ export const createWorkspaceToolInvoker = (input: { · 38 unmodified lines
115 sourceCatalogSyncService: input.sourceCatalogSyncService,129 sourceCatalogSyncService: input.sourceCatalogSyncService,
116 sourceAuthService: input.sourceAuthService,130 sourceAuthService: input.sourceAuthService,
117 installationStore: input.installationStore,131 installationStore: input.installationStore,
132 instanceConfigResolver: input.instanceConfigResolver,
133 storeSecretMaterial: input.storeSecretMaterial,
134 deleteSecretMaterial: input.deleteSecretMaterial,
135 updateSecretMaterial: input.updateSecretMaterial,
118 workspaceConfigStore: input.workspaceConfigStore,136 workspaceConfigStore: input.workspaceConfigStore,
119 workspaceStateStore: input.workspaceStateStore,137 workspaceStateStore: input.workspaceStateStore,
120 sourceArtifactStore: input.sourceArtifactStore,138 sourceArtifactStore: input.sourceArtifactStore,
packages/platform/sdk/src/runtime/index.ts+82−32
@@ -13,16 +13,22 @@ import { RuntimeSourceAuthMaterialLive } from "./auth/source-auth-material"; · 12 unmodified lines
13import { RuntimeSourceCatalogStoreLive } from "./catalog/source/runtime";13import { RuntimeSourceCatalogStoreLive } from "./catalog/source/runtime";
14import { reconcileMissingSourceCatalogArtifacts } from "./catalog/source/reconcile";14import { reconcileMissingSourceCatalogArtifacts } from "./catalog/source/reconcile";
15import { RuntimeSourceCatalogSyncLive } from "./catalog/source/sync";15import { RuntimeSourceCatalogSyncLive } from "./catalog/source/sync";
16import {
17 SourceTypeDeclarationsRefresherService,
18 type SourceTypeDeclarationsRefresherShape,
19} from "./catalog/source/type-declarations";
16import { createLiveExecutionManager, LiveExecutionManagerService } from "./execution/live";20import { createLiveExecutionManager, LiveExecutionManagerService } from "./execution/live";
17import { RuntimeExecutionResolverLive } from "./execution/workspace/environment";21import { RuntimeExecutionResolverLive } from "./execution/workspace/environment";
18import type { CreateWorkspaceInternalToolMap, WorkspaceInternalToolContext } from "./execution/workspace/tool-invoker";22import type { CreateWorkspaceInternalToolMap, WorkspaceInternalToolContext } from "./execution/workspace/tool-invoker";
19import type {23import type { LoadedLocalExecutorConfig } from "./workspace-config";
20 LoadedLocalExecutorConfig,
21 ResolvedLocalWorkspaceContext,
22} from "./local/config";
23import type { LocalExecutorConfig } from "#schema";24import type { LocalExecutorConfig } from "#schema";
24import type { LocalControlPlanePersistence } from "./local/control-plane-store";25import type { InstanceConfig } from "../local/contracts";
26import type {
27 ExecutorWorkspaceContext,
28 ExecutorWorkspaceDescriptor,
29} from "../workspace";
25import {30import {
31 LocalInstanceConfigService,
26 type DeleteSecretMaterial,32 type DeleteSecretMaterial,
27 type ResolveSecretMaterial,33 type ResolveSecretMaterial,
28 SecretMaterialDeleterService,34 SecretMaterialDeleterService,
@@ -31,17 +37,17 @@ import { · 2 unmodified lines
31 SecretMaterialUpdaterService,37 SecretMaterialUpdaterService,
32 type StoreSecretMaterial,38 type StoreSecretMaterial,
33 type UpdateSecretMaterial,39 type UpdateSecretMaterial,
34} from "./local/secret-material-providers";40} from "./workspace/secret-material-providers";
35import type { LocalSourceArtifact } from "./local/source-artifacts";41import type { LocalSourceArtifact } from "./source-artifacts";
36import {42import {
37 type RuntimeLocalWorkspaceState,43 type RuntimeLocalWorkspaceState,
38 RuntimeLocalWorkspaceLive,44 RuntimeLocalWorkspaceLive,
39} from "./local/runtime-context";45} from "./workspace/runtime-context";
40import {46import {
41 type LocalToolRuntime,47 type LocalToolRuntime,
42 type LocalToolRuntimeLoaderShape,48 type LocalToolRuntimeLoaderShape,
43 LocalToolRuntimeLoaderService,49 LocalToolRuntimeLoaderService,
44} from "./local/tools";50} from "./local-tool-runtime";
45import {51import {
46 InstallationStore,52 InstallationStore,
47 makeLocalStorageLayer,53 makeLocalStorageLayer,
@@ -49,9 +55,9 @@ import { · 1 unmodified line
49 type SourceArtifactStoreShape,55 type SourceArtifactStoreShape,
50 type WorkspaceConfigStoreShape,56 type WorkspaceConfigStoreShape,
51 type WorkspaceStateStoreShape,57 type WorkspaceStateStoreShape,
52} from "./local/storage";58} from "./workspace/storage";
53import type { LocalWorkspaceState } from "./local/workspace-state";59import type { LocalWorkspaceState } from "./workspace-state";
54import { synchronizeLocalWorkspaceState } from "./local/workspace-sync";60import { synchronizeLocalWorkspaceState } from "./workspace/workspace-sync";
55import { ControlPlaneStore, type ControlPlaneStoreShape } from "./store";61import { ControlPlaneStore, type ControlPlaneStoreShape } from "./store";
56import { RuntimeSourceAuthServiceLive } from "./sources/source-auth-service";62import { RuntimeSourceAuthServiceLive } from "./sources/source-auth-service";
57import { RuntimeSourceStoreLive } from "./sources/source-store";63import { RuntimeSourceStoreLive } from "./sources/source-store";
@@ -59,19 +65,10 @@ import { RuntimeSourceStoreLive } from "./sources/source-store"; · 1 unmodified line
59export * from "./execution/state";65export * from "./execution/state";
60export * from "./sources/executor-tools";66export * from "./sources/executor-tools";
61export * from "./execution/live";67export * from "./execution/live";
62export * from "./local/config";
63export * from "./local/control-plane-store";
64export * from "./local/errors";
65export * from "./local/installation";
66export * from "./local/runtime-context";
67export * from "./local/storage";
68export * from "./local/source-artifacts";
69export * from "./local/tools";
70export * from "./catalog/schema-type-signature";68export * from "./catalog/schema-type-signature";
71export * from "./catalog/source/runtime";69export * from "./catalog/source/runtime";
72export * from "./catalog/source/sync";70export * from "./catalog/source/sync";
73export * from "./sources/source-auth-service";71export * from "./sources/source-auth-service";
74export * from "./local/secret-material-providers";
75export * from "./sources/source-credential-interactions";72export * from "./sources/source-credential-interactions";
76export * from "./sources/source-adapters/mcp";73export * from "./sources/source-adapters/mcp";
77export * from "./sources/source-store";74export * from "./sources/source-store";
@@ -84,6 +81,20 @@ export type { · 6 unmodified lines
84 CreateWorkspaceInternalToolMap,81 CreateWorkspaceInternalToolMap,
85 WorkspaceInternalToolContext,82 WorkspaceInternalToolContext,
86} from "./execution/workspace/tool-invoker";83} from "./execution/workspace/tool-invoker";
84export {
85 LocalInstanceConfigService,
86 SecretMaterialDeleterService,
87 SecretMaterialResolverService,
88 SecretMaterialStorerService,
89 SecretMaterialUpdaterService,
90} from "./workspace/secret-material-providers";
91export type {
92 DeleteSecretMaterial,
93 ResolveInstanceConfig,
94 ResolveSecretMaterial,
95 StoreSecretMaterial,
96 UpdateSecretMaterial,
97} from "./workspace/secret-material-providers";
87export {98export {
88 builtInSourceAdapters,99 builtInSourceAdapters,
89 connectableSourceAdapters,100 connectableSourceAdapters,
@@ -154,6 +165,9 @@ export type BoundLocalToolRuntimeLoader = { · 64 unmodified lines
154 load: () => ReturnType<LocalToolRuntimeLoaderShape["load"]>;165 load: () => ReturnType<LocalToolRuntimeLoaderShape["load"]>;
155};166};
156 167
168export type BoundSourceTypeDeclarationsRefresher =
169 SourceTypeDeclarationsRefresherShape;
170
157export type RuntimeSecretMaterialServices = {171export type RuntimeSecretMaterialServices = {
158 resolve: ResolveSecretMaterial;172 resolve: ResolveSecretMaterial;
159 store: StoreSecretMaterial;173 store: StoreSecretMaterial;
@@ -161,15 +175,26 @@ export type RuntimeSecretMaterialServices = { · 1 unmodified line
161 update: UpdateSecretMaterial;175 update: UpdateSecretMaterial;
162};176};
163 177
178export type RuntimeInstanceConfigService = {
179 resolve: () => Effect.Effect<InstanceConfig, Error, never>;
180};
181
182export type RuntimePersistence = {
183 rows: ControlPlaneStoreShape;
184 close: () => Promise<void>;
185};
186
164export type RuntimeControlPlaneServices = {187export type RuntimeControlPlaneServices = {
165 workspaceContext: ResolvedLocalWorkspaceContext;188 workspace: ExecutorWorkspaceDescriptor;
166 installationStore: BoundInstallationStore;189 installationStore: BoundInstallationStore;
167 workspaceConfigStore: BoundWorkspaceConfigStore;190 workspaceConfigStore: BoundWorkspaceConfigStore;
168 workspaceStateStore: BoundWorkspaceStateStore;191 workspaceStateStore: BoundWorkspaceStateStore;
169 sourceArtifactStore: BoundSourceArtifactStore;192 sourceArtifactStore: BoundSourceArtifactStore;
170 localToolRuntimeLoader?: BoundLocalToolRuntimeLoader;193 localToolRuntimeLoader?: BoundLocalToolRuntimeLoader;
194 sourceTypeDeclarationsRefresher?: BoundSourceTypeDeclarationsRefresher;
171 secretMaterial: RuntimeSecretMaterialServices;195 secretMaterial: RuntimeSecretMaterialServices;
172 persistence: LocalControlPlanePersistence;196 instanceConfig: RuntimeInstanceConfigService;
197 persistence: RuntimePersistence;
173};198};
174 199
175const emptyToolRuntime = (): LocalToolRuntime => {200const emptyToolRuntime = (): LocalToolRuntime => {
@@ -182,17 +207,22 @@ const emptyToolRuntime = (): LocalToolRuntime => { · 6 unmodified lines
182 };207 };
183};208};
184 209
210const noopSourceTypeDeclarationsRefresher: BoundSourceTypeDeclarationsRefresher = {
211 refreshWorkspaceInBackground: () => Effect.void,
212 refreshSourceInBackground: () => Effect.void,
213};
214
185const toInstallationStoreShape = (215const toInstallationStoreShape = (
186 input: BoundInstallationStore,216 input: BoundInstallationStore,
187): InstallationStoreShape => ({217): InstallationStoreShape => ({
188 load: () => input.load(),218 load: input.load,
189 getOrProvision: () => input.getOrProvision(),219 getOrProvision: input.getOrProvision,
190});220});
191 221
192const toWorkspaceConfigStoreShape = (222const toWorkspaceConfigStoreShape = (
193 input: BoundWorkspaceConfigStore,223 input: BoundWorkspaceConfigStore,
194): WorkspaceConfigStoreShape => ({224): WorkspaceConfigStoreShape => ({
195 load: () => input.load(),225 load: input.load,
196 writeProject: ({ config }) => input.writeProject(config),226 writeProject: ({ config }) => input.writeProject(config),
197 resolveRelativePath: input.resolveRelativePath,227 resolveRelativePath: input.resolveRelativePath,
198});228});
@@ -200,7 +230,7 @@ const toWorkspaceConfigStoreShape = ( · 1 unmodified line
200const toWorkspaceStateStoreShape = (230const toWorkspaceStateStoreShape = (
201 input: BoundWorkspaceStateStore,231 input: BoundWorkspaceStateStore,
202): WorkspaceStateStoreShape => ({232): WorkspaceStateStoreShape => ({
203 load: () => input.load(),233 load: input.load,
204 write: ({ state }) => input.write(state),234 write: ({ state }) => input.write(state),
205});235});
206 236
@@ -221,6 +251,9 @@ const makeSecretMaterialLayer = (input: RuntimeSecretMaterialServices) => · 14 unmodified lines
221 Layer.succeed(SecretMaterialUpdaterService, input.update),251 Layer.succeed(SecretMaterialUpdaterService, input.update),
222 );252 );
223 253
254const makeInstanceConfigLayer = (input: RuntimeInstanceConfigService) =>
255 Layer.succeed(LocalInstanceConfigService, input.resolve);
256
224export const createRuntimeControlPlaneLayer = (257export const createRuntimeControlPlaneLayer = (
225 input: RuntimeControlPlaneOptions & RuntimeControlPlaneServices & {258 input: RuntimeControlPlaneOptions & RuntimeControlPlaneServices & {
226 store: ControlPlaneStoreShape;259 store: ControlPlaneStoreShape;
@@ -241,19 +274,30 @@ export const createRuntimeControlPlaneLayer = ( · 14 unmodified lines
241 input.localToolRuntimeLoader?.load() ?? Effect.succeed(emptyToolRuntime()),274 input.localToolRuntimeLoader?.load() ?? Effect.succeed(emptyToolRuntime()),
242 }),275 }),
243 );276 );
277 const sourceTypeDeclarationsRefresherLayer = Layer.succeed(
278 SourceTypeDeclarationsRefresherService,
279 SourceTypeDeclarationsRefresherService.of(
280 input.sourceTypeDeclarationsRefresher
281 ?? noopSourceTypeDeclarationsRefresher,
282 ),
283 );
244 284
245 const baseLayer = Layer.mergeAll(285 const baseLayer = Layer.mergeAll(
246 Layer.succeed(ControlPlaneStore, input.store),286 Layer.succeed(ControlPlaneStore, input.store),
247 RuntimeLocalWorkspaceLive(input.localWorkspaceState),287 RuntimeLocalWorkspaceLive(input.localWorkspaceState),
248 storageLayer,288 storageLayer,
249 Layer.succeed(LiveExecutionManagerService, input.liveExecutionManager),289 Layer.succeed(LiveExecutionManagerService, input.liveExecutionManager),
290 sourceTypeDeclarationsRefresherLayer,
250 );291 );
251 292
252 const secretMaterialLayer = makeSecretMaterialLayer(input.secretMaterial).pipe(293 const secretMaterialLayer = makeSecretMaterialLayer(input.secretMaterial).pipe(
253 Layer.provide(baseLayer),294 Layer.provide(baseLayer),
254 );295 );
296 const instanceConfigLayer = makeInstanceConfigLayer(input.instanceConfig);
255 297
256 const sourceStoreLayer = RuntimeSourceStoreLive.pipe(Layer.provide(baseLayer));298 const sourceStoreLayer = RuntimeSourceStoreLive.pipe(
299 Layer.provide(Layer.mergeAll(baseLayer, secretMaterialLayer)),
300 );
257 301
258 const sourceCatalogStoreLayer = RuntimeSourceCatalogStoreLive.pipe(302 const sourceCatalogStoreLayer = RuntimeSourceCatalogStoreLive.pipe(
259 Layer.provide(Layer.mergeAll(baseLayer, sourceStoreLayer)),303 Layer.provide(Layer.mergeAll(baseLayer, sourceStoreLayer)),
@@ -275,6 +319,7 @@ export const createRuntimeControlPlaneLayer = ( · 15 unmodified lines
275 Layer.provide(319 Layer.provide(
276 Layer.mergeAll(320 Layer.mergeAll(
277 baseLayer,321 baseLayer,
322 instanceConfigLayer,
278 secretMaterialLayer,323 secretMaterialLayer,
279 sourceStoreLayer,324 sourceStoreLayer,
280 sourceCatalogSyncLayer,325 sourceCatalogSyncLayer,
@@ -289,6 +334,7 @@ export const createRuntimeControlPlaneLayer = ( · 8 unmodified lines
289 Layer.provide(334 Layer.provide(
290 Layer.mergeAll(335 Layer.mergeAll(
291 baseLayer,336 baseLayer,
337 instanceConfigLayer,
292 secretMaterialLayer,338 secretMaterialLayer,
293 sourceStoreLayer,339 sourceStoreLayer,
294 sourceAuthMaterialLayer,340 sourceAuthMaterialLayer,
@@ -302,6 +348,7 @@ export const createRuntimeControlPlaneLayer = ( · 7 unmodified lines
302 348
303 return Layer.mergeAll(349 return Layer.mergeAll(
304 baseLayer,350 baseLayer,
351 instanceConfigLayer,
305 secretMaterialLayer,352 secretMaterialLayer,
306 sourceStoreLayer,353 sourceStoreLayer,
307 sourceAuthMaterialLayer,354 sourceAuthMaterialLayer,
@@ -314,7 +361,7 @@ export const createRuntimeControlPlaneLayer = ( · 6 unmodified lines
314};361};
315 362
316export type ControlPlaneRuntime = {363export type ControlPlaneRuntime = {
317 persistence: LocalControlPlanePersistence;364 persistence: RuntimePersistence;
318 localInstallation: LocalInstallation;365 localInstallation: LocalInstallation;
319 managedRuntime: ManagedRuntime.ManagedRuntime<any, never>;366 managedRuntime: ManagedRuntime.ManagedRuntime<any, never>;
320 runtimeLayer: RuntimeControlPlaneLayer;367 runtimeLayer: RuntimeControlPlaneLayer;
@@ -339,8 +386,12 @@ export const createControlPlaneRuntimeFromServices = (input: { · 18 unmodified lines
339 .load()386 .load()
340 .pipe(Effect.mapError(toRuntimeBootstrapError));387 .pipe(Effect.mapError(toRuntimeBootstrapError));
341 388
389 const runtimeWorkspace: ExecutorWorkspaceContext = {
390 ...input.services.workspace,
391 workspaceId: localInstallation.workspaceId,
392 accountId: localInstallation.accountId,
393 };
342 const effectiveLocalConfig = yield* synchronizeLocalWorkspaceState({394 const effectiveLocalConfig = yield* synchronizeLocalWorkspaceState({
343 context: input.services.workspaceContext,
344 loadedConfig: loadedLocalConfig,395 loadedConfig: loadedLocalConfig,
345 })396 })
346 .pipe(397 .pipe(
@@ -362,9 +413,8 @@ export const createControlPlaneRuntimeFromServices = (input: { · 15 unmodified lines
362 ),413 ),
363 )414 )
364 .pipe(Effect.mapError(toRuntimeBootstrapError));415 .pipe(Effect.mapError(toRuntimeBootstrapError));
365
366 const runtimeLocalWorkspaceState: RuntimeLocalWorkspaceState = {416 const runtimeLocalWorkspaceState: RuntimeLocalWorkspaceState = {
367 context: input.services.workspaceContext,417 workspace: runtimeWorkspace,
368 installation: {418 installation: {
369 workspaceId: localInstallation.workspaceId,419 workspaceId: localInstallation.workspaceId,
370 accountId: localInstallation.accountId,420 accountId: localInstallation.accountId,
packages/platform/sdk/src/runtime/local-tool-runtime.ts+37−0
@@ -0,0 +1,37 @@
1import type {
2 ToolCatalog,
3 ToolInvoker,
4 ToolMap,
5} from "@executor/codemode-core";
6import * as Context from "effect/Context";
7import * as Effect from "effect/Effect";
8import * as Layer from "effect/Layer";
9
10export type LocalToolRuntime = {
11 tools: ToolMap;
12 catalog: ToolCatalog;
13 toolInvoker: ToolInvoker;
14 toolPaths: Set<string>;
15};
16
17export type LocalToolRuntimeLoaderShape = {
18 load: () => Effect.Effect<LocalToolRuntime, Error, never>;
19};
20
21export class LocalToolRuntimeLoaderService extends Context.Tag(
22 "#runtime/LocalToolRuntimeLoaderService",
23)<LocalToolRuntimeLoaderService, LocalToolRuntimeLoaderShape>() {}
24
25export const LocalToolRuntimeLoaderLive = Layer.effect(
26 LocalToolRuntimeLoaderService,
27 Effect.succeed(
28 LocalToolRuntimeLoaderService.of({
29 load: () =>
30 Effect.die(
31 new Error(
32 "LocalToolRuntimeLoaderLive is unsupported; provide a bound local tool runtime loader from the host adapter.",
33 ),
34 ),
35 }),
36 ),
37);
packages/platform/sdk/src/runtime/local/operations.ts+0−1
@@ -1 +0,0 @@
1export * from "../../local/operations";
packages/platform/sdk/src/runtime/local/source-artifacts.ts+0−377
@@ -1,377 +0,0 @@
1import { join } from "node:path";
2import { FileSystem } from "@effect/platform";
3
4import {
5 SourceIdSchema,
6 SourceCatalogIdSchema,
7 StoredSourceCatalogRevisionRecordSchema,
8 TimestampMsSchema,
9 type Source,
10 type SourceCatalogId,
11} from "#schema";
12import * as Effect from "effect/Effect";
13import * as Option from "effect/Option";
14import * as Schema from "effect/Schema";
15
16import { decodeCatalogSnapshotV1 } from "@executor/ir/catalog";
17import {
18 CatalogSnapshotV1Schema,
19 type CatalogSnapshotV1,
20 type NativeBlob,
21 type SourceDocument,
22} from "@executor/ir/model";
23import {
24 contentHash,
25 snapshotFromSourceCatalogSyncResult,
26 type SourceCatalogSyncResult,
27} from "@executor/source-core";
28import type { ResolvedLocalWorkspaceContext } from "./config";
29import {
30 LocalFileSystemError,
31 unknownLocalErrorDetails,
32} from "./errors";
33import {
34 createSourceCatalogRevisionRecord,
35 stableSourceCatalogId,
36} from "../sources/source-definitions";
37const LEGACY_LOCAL_SOURCE_ARTIFACT_VERSION = 3 as const;
38const LOCAL_SOURCE_ARTIFACT_VERSION = 4 as const;
39
40const LegacyLocalSourceArtifactSchema = Schema.Struct({
41 version: Schema.Literal(LEGACY_LOCAL_SOURCE_ARTIFACT_VERSION),
42 sourceId: SourceIdSchema,
43 catalogId: SourceCatalogIdSchema,
44 generatedAt: TimestampMsSchema,
45 revision: StoredSourceCatalogRevisionRecordSchema,
46 snapshot: CatalogSnapshotV1Schema,
47});
48
49export const LocalSourceArtifactSchema = Schema.Struct({
50 version: Schema.Literal(LOCAL_SOURCE_ARTIFACT_VERSION),
51 sourceId: SourceIdSchema,
52 catalogId: SourceCatalogIdSchema,
53 generatedAt: TimestampMsSchema,
54 revision: StoredSourceCatalogRevisionRecordSchema,
55 snapshot: CatalogSnapshotV1Schema,
56});
57
58export type LocalSourceArtifact = typeof LocalSourceArtifactSchema.Type;
59type LegacyLocalSourceArtifact = typeof LegacyLocalSourceArtifactSchema.Type;
60
61const ReadableLegacyLocalSourceArtifactSchema = Schema.Struct({
62 version: Schema.Literal(LEGACY_LOCAL_SOURCE_ARTIFACT_VERSION),
63 sourceId: SourceIdSchema,
64 catalogId: SourceCatalogIdSchema,
65 generatedAt: TimestampMsSchema,
66 revision: StoredSourceCatalogRevisionRecordSchema,
67 snapshot: Schema.Unknown,
68});
69
70const ReadableLocalSourceArtifactSchema = Schema.Struct({
71 version: Schema.Literal(LOCAL_SOURCE_ARTIFACT_VERSION),
72 sourceId: SourceIdSchema,
73 catalogId: SourceCatalogIdSchema,
74 generatedAt: TimestampMsSchema,
75 revision: StoredSourceCatalogRevisionRecordSchema,
76 snapshot: Schema.Unknown,
77});
78
79type ReadableLegacyLocalSourceArtifact =
80 typeof ReadableLegacyLocalSourceArtifactSchema.Type;
81type ReadableLocalSourceArtifact =
82 typeof ReadableLocalSourceArtifactSchema.Type;
83
84const decodeReadableLocalSourceArtifactOption = Schema.decodeUnknownOption(
85 Schema.parseJson(
86 Schema.Union(
87 ReadableLocalSourceArtifactSchema,
88 ReadableLegacyLocalSourceArtifactSchema,
89 ),
90 ),
91);
92
93const normalizeLocalSourceArtifact = (
94 artifact: ReadableLocalSourceArtifact | ReadableLegacyLocalSourceArtifact,
95): Omit<LocalSourceArtifact, "snapshot"> & { snapshot: unknown } =>
96 artifact.version === LOCAL_SOURCE_ARTIFACT_VERSION
97 ? artifact
98 : {
99 ...artifact,
100 version: LOCAL_SOURCE_ARTIFACT_VERSION,
101 };
102
103const mapFileSystemError = (path: string, action: string) => (cause: unknown) =>
104 new LocalFileSystemError({
105 message: `Failed to ${action} ${path}: ${unknownLocalErrorDetails(cause)}`,
106 action,
107 path,
108 details: unknownLocalErrorDetails(cause),
109 });
110
111const mutableRecord = <K extends string, V>(value: Readonly<Record<K, V>>): Record<K, V> =>
112 value as Record<K, V>;
113
114const localSourceArtifactPath = (input: {
115 context: ResolvedLocalWorkspaceContext;
116 sourceId: string;
117}): string =>
118 join(
119 input.context.artifactsDirectory,
120 "sources",
121 `${input.sourceId}.json`,
122 );
123
124const localSourceDocumentDirectory = (input: {
125 context: ResolvedLocalWorkspaceContext;
126 sourceId: string;
127}): string =>
128 join(input.context.artifactsDirectory, "sources", input.sourceId, "documents");
129
130const localSourceDocumentPath = (input: {
131 context: ResolvedLocalWorkspaceContext;
132 sourceId: string;
133 documentId: string;
134}): string =>
135 join(localSourceDocumentDirectory(input), `${input.documentId}.txt`);
136
137const splitArtifactSourceDocuments = (artifact: LocalSourceArtifact): {
138 artifact: LocalSourceArtifact;
139 rawDocuments: ReadonlyArray<{
140 documentId: string;
141 blob: NativeBlob;
142 content: string;
143 }>;
144} => {
145 const nextArtifact = structuredClone(artifact);
146 const rawDocuments: Array<{
147 documentId: string;
148 blob: NativeBlob;
149 content: string;
150 }> = [];
151
152 for (const [documentId, document] of Object.entries(nextArtifact.snapshot.catalog.documents)) {
153 const mutableDocument = document as SourceDocument & { native?: NativeBlob[] };
154 const sourceDocumentBlob = document.native?.find((blob) =>
155 blob.kind === "source_document" && typeof blob.value === "string"
156 );
157 if (!sourceDocumentBlob || typeof sourceDocumentBlob.value !== "string") {
158 continue;
159 }
160
161 rawDocuments.push({
162 documentId,
163 blob: sourceDocumentBlob,
164 content: sourceDocumentBlob.value,
165 });
166
167 const remainingBlobs = (document.native ?? []).filter((blob) => blob !== sourceDocumentBlob);
168 if (remainingBlobs.length > 0) {
169 mutableDocument.native = remainingBlobs;
170 } else {
171 delete mutableDocument.native;
172 }
173 }
174
175 return {
176 artifact: nextArtifact,
177 rawDocuments,
178 };
179};
180
181const hydrateArtifactSourceDocuments = (input: {
182 artifact: LocalSourceArtifact;
183 rawDocuments: Readonly<Record<string, NativeBlob>>;
184}): LocalSourceArtifact => {
185 const nextArtifact = structuredClone(input.artifact);
186 const mutableDocuments = mutableRecord(nextArtifact.snapshot.catalog.documents);
187
188 for (const [documentId, rawDocument] of Object.entries(input.rawDocuments)) {
189 const document = mutableDocuments[documentId as keyof typeof mutableDocuments] as (SourceDocument & { native?: NativeBlob[] }) | undefined;
190 if (!document) {
191 continue;
192 }
193
194 const remainingBlobs = (document.native ?? []).filter((blob) => blob.kind !== "source_document");
195 document.native = [rawDocument, ...remainingBlobs];
196 }
197
198 return nextArtifact;
199};
200
201const snapshotHash = (snapshot: CatalogSnapshotV1): string =>
202 contentHash(JSON.stringify(snapshot));
203
204const importMetadataHash = (snapshot: { import: SourceCatalogSyncResult["importMetadata"] }): string =>
205 contentHash(JSON.stringify(snapshot.import));
206
207const decodeCatalogSnapshotV1Option = (
208 snapshot: unknown,
209): CatalogSnapshotV1 | null => {
210 try {
211 return decodeCatalogSnapshotV1(snapshot);
212 } catch {
213 return null;
214 }
215};
216
217export const buildLocalSourceArtifact = (input: {
218 source: Source;
219 syncResult: SourceCatalogSyncResult;
220}): LocalSourceArtifact => {
221 const catalogId: SourceCatalogId = stableSourceCatalogId(input.source);
222 const generatedAt = Date.now();
223 const snapshot = snapshotFromSourceCatalogSyncResult(input.syncResult);
224 const importHash = importMetadataHash(snapshot);
225 const hash = snapshotHash(snapshot);
226 const revision = createSourceCatalogRevisionRecord({
227 source: input.source,
228 catalogId,
229 revisionNumber: 1,
230 importMetadataJson: JSON.stringify(snapshot.import),
231 importMetadataHash: importHash,
232 snapshotHash: hash,
233 });
234
235 return {
236 version: LOCAL_SOURCE_ARTIFACT_VERSION,
237 sourceId: input.source.id,
238 catalogId,
239 generatedAt,
240 revision,
241 snapshot,
242 };
243};
244
245export const readLocalSourceArtifact = (input: {
246 context: ResolvedLocalWorkspaceContext;
247 sourceId: string;
248}): Effect.Effect<
249 LocalSourceArtifact | null,
250 LocalFileSystemError,
251 FileSystem.FileSystem
252> =>
253 Effect.gen(function* () {
254 const fs = yield* FileSystem.FileSystem;
255 const path = localSourceArtifactPath(input);
256 const exists = yield* fs.exists(path).pipe(
257 Effect.mapError(mapFileSystemError(path, "check source artifact path")),
258 );
259 if (!exists) {
260 return null;
261 }
262
263 const readPath = path;
264 const content = yield* fs.readFileString(readPath, "utf8").pipe(
265 Effect.mapError(mapFileSystemError(readPath, "read source artifact")),
266 );
267
268 const decodedArtifact = decodeReadableLocalSourceArtifactOption(content);
269 if (Option.isNone(decodedArtifact)) {
270 return null;
271 }
272
273 const artifact = normalizeLocalSourceArtifact(decodedArtifact.value);
274 const snapshot = decodeCatalogSnapshotV1Option(artifact.snapshot);
275 if (snapshot === null) {
276 return null;
277 }
278
279 const decodedSnapshotArtifact: LocalSourceArtifact = {
280 ...artifact,
281 snapshot,
282 };
283
284 const rawDocuments: Record<string, NativeBlob> = {};
285 for (const documentId of Object.keys(decodedSnapshotArtifact.snapshot.catalog.documents)) {
286 const sourceDocumentPath = localSourceDocumentPath({
287 context: input.context,
288 sourceId: input.sourceId,
289 documentId,
290 });
291 const sourceDocumentExists = yield* fs.exists(sourceDocumentPath).pipe(
292 Effect.mapError(mapFileSystemError(sourceDocumentPath, "check source document path")),
293 );
294 if (!sourceDocumentExists) {
295 continue;
296 }
297
298 const sourceContent = yield* fs.readFileString(sourceDocumentPath, "utf8").pipe(
299 Effect.mapError(mapFileSystemError(sourceDocumentPath, "read source document")),
300 );
301 rawDocuments[documentId] = {
302 sourceKind: decodedSnapshotArtifact.snapshot.import.sourceKind,
303 kind: "source_document",
304 value: sourceContent,
305 } satisfies NativeBlob;
306 }
307
308 const hydratedArtifact = Object.keys(rawDocuments).length > 0
309 ? hydrateArtifactSourceDocuments({
310 artifact: decodedSnapshotArtifact,
311 rawDocuments,
312 })
313 : decodedSnapshotArtifact;
314
315 return {
316 ...hydratedArtifact,
317 snapshot: hydratedArtifact.snapshot,
318 };
319 });
320
321export const writeLocalSourceArtifact = (input: {
322 context: ResolvedLocalWorkspaceContext;
323 sourceId: string;
324 artifact: LocalSourceArtifact;
325}): Effect.Effect<void, LocalFileSystemError, FileSystem.FileSystem> =>
326 Effect.gen(function* () {
327 const fs = yield* FileSystem.FileSystem;
328 const directory = join(input.context.artifactsDirectory, "sources");
329 const path = localSourceArtifactPath(input);
330 const sourceDocumentDirectory = localSourceDocumentDirectory(input);
331 const split = splitArtifactSourceDocuments(input.artifact);
332 yield* fs.makeDirectory(directory, { recursive: true }).pipe(
333 Effect.mapError(mapFileSystemError(directory, "create source artifact directory")),
334 );
335 yield* fs.remove(sourceDocumentDirectory, { recursive: true, force: true }).pipe(
336 Effect.mapError(mapFileSystemError(sourceDocumentDirectory, "remove source document directory")),
337 );
338 if (split.rawDocuments.length > 0) {
339 yield* fs.makeDirectory(sourceDocumentDirectory, { recursive: true }).pipe(
340 Effect.mapError(mapFileSystemError(sourceDocumentDirectory, "create source document directory")),
341 );
342 for (const rawDocument of split.rawDocuments) {
343 const sourceDocumentPath = localSourceDocumentPath({
344 context: input.context,
345 sourceId: input.sourceId,
346 documentId: rawDocument.documentId,
347 });
348 yield* fs.writeFileString(sourceDocumentPath, rawDocument.content).pipe(
349 Effect.mapError(mapFileSystemError(sourceDocumentPath, "write source document")),
350 );
351 }
352 }
353 yield* fs.writeFileString(path, `${JSON.stringify(split.artifact)}\n`).pipe(
354 Effect.mapError(mapFileSystemError(path, "write source artifact")),
355 );
356 });
357
358export const removeLocalSourceArtifact = (input: {
359 context: ResolvedLocalWorkspaceContext;
360 sourceId: string;
361}): Effect.Effect<void, LocalFileSystemError, FileSystem.FileSystem> =>
362 Effect.gen(function* () {
363 const fs = yield* FileSystem.FileSystem;
364 const path = localSourceArtifactPath(input);
365 const exists = yield* fs.exists(path).pipe(
366 Effect.mapError(mapFileSystemError(path, "check source artifact path")),
367 );
368 if (exists) {
369 yield* fs.remove(path).pipe(
370 Effect.mapError(mapFileSystemError(path, "remove source artifact")),
371 );
372 }
373 const sourceDocumentDirectory = localSourceDocumentDirectory(input);
374 yield* fs.remove(sourceDocumentDirectory, { recursive: true, force: true }).pipe(
375 Effect.mapError(mapFileSystemError(sourceDocumentDirectory, "remove source document directory")),
376 );
377 });
packages/platform/sdk/src/runtime/local/storage.ts+0−225
@@ -1,225 +0,0 @@
1import { FileSystem } from "@effect/platform";
2import { NodeFileSystem } from "@effect/platform-node";
3import type { LocalInstallation, LocalExecutorConfig } from "#schema";
4import * as Context from "effect/Context";
5import * as Effect from "effect/Effect";
6import * as Layer from "effect/Layer";
7
8import type {
9 LoadedLocalExecutorConfig,
10 ResolvedLocalWorkspaceContext,
11} from "./config";
12import {
13 loadLocalExecutorConfig,
14 resolveConfigRelativePath,
15 writeProjectLocalExecutorConfig,
16} from "./config";
17import {
18 getOrProvisionLocalInstallation,
19 loadLocalInstallation,
20} from "./installation";
21import type {
22 LocalSourceArtifact,
23} from "./source-artifacts";
24import {
25 buildLocalSourceArtifact,
26 readLocalSourceArtifact,
27 removeLocalSourceArtifact,
28 writeLocalSourceArtifact,
29} from "./source-artifacts";
30import type { LocalWorkspaceState } from "./workspace-state";
31import {
32 loadLocalWorkspaceState,
33 writeLocalWorkspaceState,
34} from "./workspace-state";
35import type { SourceCatalogSyncResult } from "@executor/source-core";
36import type { Source } from "#schema";
37
38export type InstallationStoreShape = {
39 load: (
40 context: ResolvedLocalWorkspaceContext,
41 ) => Effect.Effect<LocalInstallation, Error, never>;
42 getOrProvision: (input: {
43 context: ResolvedLocalWorkspaceContext;
44 }) => Effect.Effect<LocalInstallation, Error, never>;
45};
46
47export class InstallationStore extends Context.Tag(
48 "#runtime/InstallationStore",
49)<InstallationStore, InstallationStoreShape>() {}
50
51export type WorkspaceConfigStoreShape = {
52 load: (
53 context: ResolvedLocalWorkspaceContext,
54 ) => Effect.Effect<LoadedLocalExecutorConfig, Error, never>;
55 writeProject: (input: {
56 context: ResolvedLocalWorkspaceContext;
57 config: LocalExecutorConfig;
58 }) => Effect.Effect<void, Error, never>;
59 resolveRelativePath: (input: { path: string; workspaceRoot: string }) => string;
60};
61
62export class WorkspaceConfigStore extends Context.Tag(
63 "#runtime/WorkspaceConfigStore",
64)<WorkspaceConfigStore, WorkspaceConfigStoreShape>() {}
65
66export type WorkspaceStateStoreShape = {
67 load: (
68 context: ResolvedLocalWorkspaceContext,
69 ) => Effect.Effect<LocalWorkspaceState, Error, never>;
70 write: (input: {
71 context: ResolvedLocalWorkspaceContext;
72 state: LocalWorkspaceState;
73 }) => Effect.Effect<void, Error, never>;
74};
75
76export class WorkspaceStateStore extends Context.Tag(
77 "#runtime/WorkspaceStateStore",
78)<WorkspaceStateStore, WorkspaceStateStoreShape>() {}
79
80export type SourceArtifactStoreShape = {
81 build: (input: {
82 source: Source;
83 syncResult: SourceCatalogSyncResult;
84 }) => LocalSourceArtifact;
85 read: (input: {
86 context: ResolvedLocalWorkspaceContext;
87 sourceId: string;
88 }) => Effect.Effect<LocalSourceArtifact | null, Error, never>;
89 write: (input: {
90 context: ResolvedLocalWorkspaceContext;
91 sourceId: string;
92 artifact: LocalSourceArtifact;
93 }) => Effect.Effect<void, Error, never>;
94 remove: (input: {
95 context: ResolvedLocalWorkspaceContext;
96 sourceId: string;
97 }) => Effect.Effect<void, Error, never>;
98};
99
100export class SourceArtifactStore extends Context.Tag(
101 "#runtime/SourceArtifactStore",
102)<SourceArtifactStore, SourceArtifactStoreShape>() {}
103
104export type LocalStorageServices =
105 | InstallationStore
106 | WorkspaceConfigStore
107 | WorkspaceStateStore
108 | SourceArtifactStore;
109
110export type WorkspaceStorageServices =
111 | WorkspaceConfigStore
112 | WorkspaceStateStore
113 | SourceArtifactStore;
114
115export const LocalInstallationStore: InstallationStoreShape = {
116 load: loadLocalInstallation,
117 getOrProvision: getOrProvisionLocalInstallation,
118};
119
120export const LocalInstallationStoreLive = Layer.succeed(
121 InstallationStore,
122 LocalInstallationStore,
123);
124
125const bindFileSystem = <A, E>(
126 fileSystem: FileSystem.FileSystem,
127 effect: Effect.Effect<A, E, FileSystem.FileSystem>,
128): Effect.Effect<A, E, never> =>
129 effect.pipe(Effect.provideService(FileSystem.FileSystem, fileSystem));
130
131const bindNodeFileSystem = <A, E>(
132 effect: Effect.Effect<A, E, FileSystem.FileSystem>,
133): Effect.Effect<A, E, never> =>
134 effect.pipe(Effect.provide(NodeFileSystem.layer));
135
136export const LocalWorkspaceConfigStore: WorkspaceConfigStoreShape = {
137 load: (context) => bindNodeFileSystem(loadLocalExecutorConfig(context)),
138 writeProject: (input) => bindNodeFileSystem(writeProjectLocalExecutorConfig(input)),
139 resolveRelativePath: resolveConfigRelativePath,
140};
141
142export const LocalWorkspaceConfigStoreLive = Layer.effect(
143 WorkspaceConfigStore,
144 Effect.gen(function* () {
145 const fileSystem = yield* FileSystem.FileSystem;
146
147 return WorkspaceConfigStore.of({
148 load: (context) => bindFileSystem(fileSystem, loadLocalExecutorConfig(context)),
149 writeProject: (input) =>
150 bindFileSystem(fileSystem, writeProjectLocalExecutorConfig(input)),
151 resolveRelativePath: resolveConfigRelativePath,
152 });
153 }),
154);
155
156export const LocalWorkspaceStateStore: WorkspaceStateStoreShape = {
157 load: (context) => bindNodeFileSystem(loadLocalWorkspaceState(context)),
158 write: (input) => bindNodeFileSystem(writeLocalWorkspaceState(input)),
159};
160
161export const LocalWorkspaceStateStoreLive = Layer.effect(
162 WorkspaceStateStore,
163 Effect.gen(function* () {
164 const fileSystem = yield* FileSystem.FileSystem;
165
166 return WorkspaceStateStore.of({
167 load: (context) => bindFileSystem(fileSystem, loadLocalWorkspaceState(context)),
168 write: (input) => bindFileSystem(fileSystem, writeLocalWorkspaceState(input)),
169 });
170 }),
171);
172
173export const LocalSourceArtifactStore: SourceArtifactStoreShape = {
174 build: buildLocalSourceArtifact,
175 read: (input) => bindNodeFileSystem(readLocalSourceArtifact(input)),
176 write: (input) => bindNodeFileSystem(writeLocalSourceArtifact(input)),
177 remove: (input) => bindNodeFileSystem(removeLocalSourceArtifact(input)),
178};
179
180export const LocalSourceArtifactStoreLive = Layer.effect(
181 SourceArtifactStore,
182 Effect.gen(function* () {
183 const fileSystem = yield* FileSystem.FileSystem;
184
185 return SourceArtifactStore.of({
186 build: buildLocalSourceArtifact,
187 read: (input) => bindFileSystem(fileSystem, readLocalSourceArtifact(input)),
188 write: (input) => bindFileSystem(fileSystem, writeLocalSourceArtifact(input)),
189 remove: (input) => bindFileSystem(fileSystem, removeLocalSourceArtifact(input)),
190 });
191 }),
192);
193
194export const makeWorkspaceStorageLayer = (input: {
195 workspaceConfigStore: WorkspaceConfigStoreShape;
196 workspaceStateStore: WorkspaceStateStoreShape;
197 sourceArtifactStore: SourceArtifactStoreShape;
198}) =>
199 Layer.mergeAll(
200 Layer.succeed(WorkspaceConfigStore, input.workspaceConfigStore),
201 Layer.succeed(WorkspaceStateStore, input.workspaceStateStore),
202 Layer.succeed(SourceArtifactStore, input.sourceArtifactStore),
203 );
204
205export const makeLocalStorageLayer = (input: {
206 installationStore: InstallationStoreShape;
207 workspaceConfigStore: WorkspaceConfigStoreShape;
208 workspaceStateStore: WorkspaceStateStoreShape;
209 sourceArtifactStore: SourceArtifactStoreShape;
210}) =>
211 Layer.mergeAll(
212 Layer.succeed(InstallationStore, input.installationStore),
213 makeWorkspaceStorageLayer(input),
214 );
215
216export const WorkspaceStorageLive = Layer.mergeAll(
217 LocalWorkspaceConfigStoreLive,
218 LocalWorkspaceStateStoreLive,
219 LocalSourceArtifactStoreLive,
220);
221
222export const LocalStorageLive = Layer.mergeAll(
223 LocalInstallationStoreLive,
224 WorkspaceStorageLive,
225);
packages/platform/sdk/src/runtime/policy/policies-operations.ts+0−1
@@ -1 +0,0 @@
1export * from "../../policies/operations";
packages/platform/sdk/src/runtime/source-artifacts.ts+234−0
@@ -0,0 +1,234 @@
1import {
2 SourceIdSchema,
3 SourceCatalogIdSchema,
4 StoredSourceCatalogRevisionRecordSchema,
5 TimestampMsSchema,
6 type Source,
7 type SourceCatalogId,
8} from "#schema";
9import * as Option from "effect/Option";
10import * as Schema from "effect/Schema";
11
12import { decodeCatalogSnapshotV1 } from "@executor/ir/catalog";
13import {
14 CatalogSnapshotV1Schema,
15 type CatalogSnapshotV1,
16 type NativeBlob,
17 type SourceDocument,
18} from "@executor/ir/model";
19import {
20 contentHash,
21 snapshotFromSourceCatalogSyncResult,
22 type SourceCatalogSyncResult,
23} from "@executor/source-core";
24import {
25 createSourceCatalogRevisionRecord,
26 stableSourceCatalogId,
27} from "./sources/source-definitions";
28
29const LEGACY_LOCAL_SOURCE_ARTIFACT_VERSION = 3 as const;
30const LOCAL_SOURCE_ARTIFACT_VERSION = 4 as const;
31
32const LegacyLocalSourceArtifactSchema = Schema.Struct({
33 version: Schema.Literal(LEGACY_LOCAL_SOURCE_ARTIFACT_VERSION),
34 sourceId: SourceIdSchema,
35 catalogId: SourceCatalogIdSchema,
36 generatedAt: TimestampMsSchema,
37 revision: StoredSourceCatalogRevisionRecordSchema,
38 snapshot: CatalogSnapshotV1Schema,
39});
40
41export const LocalSourceArtifactSchema = Schema.Struct({
42 version: Schema.Literal(LOCAL_SOURCE_ARTIFACT_VERSION),
43 sourceId: SourceIdSchema,
44 catalogId: SourceCatalogIdSchema,
45 generatedAt: TimestampMsSchema,
46 revision: StoredSourceCatalogRevisionRecordSchema,
47 snapshot: CatalogSnapshotV1Schema,
48});
49
50export type LocalSourceArtifact = typeof LocalSourceArtifactSchema.Type;
51type LegacyLocalSourceArtifact = typeof LegacyLocalSourceArtifactSchema.Type;
52
53const ReadableLegacyLocalSourceArtifactSchema = Schema.Struct({
54 version: Schema.Literal(LEGACY_LOCAL_SOURCE_ARTIFACT_VERSION),
55 sourceId: SourceIdSchema,
56 catalogId: SourceCatalogIdSchema,
57 generatedAt: TimestampMsSchema,
58 revision: StoredSourceCatalogRevisionRecordSchema,
59 snapshot: Schema.Unknown,
60});
61
62const ReadableLocalSourceArtifactSchema = Schema.Struct({
63 version: Schema.Literal(LOCAL_SOURCE_ARTIFACT_VERSION),
64 sourceId: SourceIdSchema,
65 catalogId: SourceCatalogIdSchema,
66 generatedAt: TimestampMsSchema,
67 revision: StoredSourceCatalogRevisionRecordSchema,
68 snapshot: Schema.Unknown,
69});
70
71type ReadableLegacyLocalSourceArtifact =
72 typeof ReadableLegacyLocalSourceArtifactSchema.Type;
73type ReadableLocalSourceArtifact =
74 typeof ReadableLocalSourceArtifactSchema.Type;
75
76const decodeReadableLocalSourceArtifactOption = Schema.decodeUnknownOption(
77 Schema.parseJson(
78 Schema.Union(
79 ReadableLocalSourceArtifactSchema,
80 ReadableLegacyLocalSourceArtifactSchema,
81 ),
82 ),
83);
84
85const normalizeLocalSourceArtifact = (
86 artifact: ReadableLocalSourceArtifact | ReadableLegacyLocalSourceArtifact,
87): Omit<LocalSourceArtifact, "snapshot"> & { snapshot: unknown } =>
88 artifact.version === LOCAL_SOURCE_ARTIFACT_VERSION
89 ? artifact
90 : {
91 ...artifact,
92 version: LOCAL_SOURCE_ARTIFACT_VERSION,
93 };
94
95const mutableRecord = <K extends string, V>(
96 value: Readonly<Record<K, V>>,
97): Record<K, V> => value as Record<K, V>;
98
99export type SourceArtifactDocumentContent = {
100 documentId: string;
101 blob: NativeBlob;
102 content: string;
103};
104
105export const splitLocalSourceArtifactDocuments = (
106 artifact: LocalSourceArtifact,
107): {
108 artifact: LocalSourceArtifact;
109 rawDocuments: readonly SourceArtifactDocumentContent[];
110} => {
111 const nextArtifact = structuredClone(artifact);
112 const rawDocuments: Array<SourceArtifactDocumentContent> = [];
113
114 for (const [documentId, document] of Object.entries(
115 nextArtifact.snapshot.catalog.documents,
116 )) {
117 const mutableDocument = document as SourceDocument & { native?: NativeBlob[] };
118 const sourceDocumentBlob = document.native?.find(
119 (blob) => blob.kind === "source_document" && typeof blob.value === "string",
120 );
121 if (!sourceDocumentBlob || typeof sourceDocumentBlob.value !== "string") {
122 continue;
123 }
124
125 rawDocuments.push({
126 documentId,
127 blob: sourceDocumentBlob,
128 content: sourceDocumentBlob.value,
129 });
130
131 const remainingBlobs = (document.native ?? []).filter(
132 (blob) => blob !== sourceDocumentBlob,
133 );
134 if (remainingBlobs.length > 0) {
135 mutableDocument.native = remainingBlobs;
136 } else {
137 delete mutableDocument.native;
138 }
139 }
140
141 return {
142 artifact: nextArtifact,
143 rawDocuments,
144 };
145};
146
147export const hydrateLocalSourceArtifactDocuments = (input: {
148 artifact: LocalSourceArtifact;
149 rawDocuments: Readonly<Record<string, NativeBlob>>;
150}): LocalSourceArtifact => {
151 const nextArtifact = structuredClone(input.artifact);
152 const mutableDocuments = mutableRecord(nextArtifact.snapshot.catalog.documents);
153
154 for (const [documentId, rawDocument] of Object.entries(input.rawDocuments)) {
155 const document = mutableDocuments[
156 documentId as keyof typeof mutableDocuments
157 ] as (SourceDocument & { native?: NativeBlob[] }) | undefined;
158 if (!document) {
159 continue;
160 }
161
162 const remainingBlobs = (document.native ?? []).filter(
163 (blob) => blob.kind !== "source_document",
164 );
165 document.native = [rawDocument, ...remainingBlobs];
166 }
167
168 return nextArtifact;
169};
170
171const snapshotHash = (snapshot: CatalogSnapshotV1): string =>
172 contentHash(JSON.stringify(snapshot));
173
174const importMetadataHash = (snapshot: {
175 import: SourceCatalogSyncResult["importMetadata"];
176}): string => contentHash(JSON.stringify(snapshot.import));
177
178const decodeCatalogSnapshotV1Option = (
179 snapshot: unknown,
180): CatalogSnapshotV1 | null => {
181 try {
182 return decodeCatalogSnapshotV1(snapshot);
183 } catch {
184 return null;
185 }
186};
187
188export const decodeStoredLocalSourceArtifact = (
189 content: string,
190): LocalSourceArtifact | null => {
191 const decodedArtifact = decodeReadableLocalSourceArtifactOption(content);
192 if (Option.isNone(decodedArtifact)) {
193 return null;
194 }
195
196 const artifact = normalizeLocalSourceArtifact(decodedArtifact.value);
197 const snapshot = decodeCatalogSnapshotV1Option(artifact.snapshot);
198 if (snapshot === null) {
199 return null;
200 }
201
202 return {
203 ...artifact,
204 snapshot,
205 };
206};
207
208export const buildLocalSourceArtifact = (input: {
209 source: Source;
210 syncResult: SourceCatalogSyncResult;
211}): LocalSourceArtifact => {
212 const catalogId: SourceCatalogId = stableSourceCatalogId(input.source);
213 const generatedAt = Date.now();
214 const snapshot = snapshotFromSourceCatalogSyncResult(input.syncResult);
215 const importHash = importMetadataHash(snapshot);
216 const hash = snapshotHash(snapshot);
217 const revision = createSourceCatalogRevisionRecord({
218 source: input.source,
219 catalogId,
220 revisionNumber: 1,
221 importMetadataJson: JSON.stringify(snapshot.import),
222 importMetadataHash: importHash,
223 snapshotHash: hash,
224 });
225
226 return {
227 version: LOCAL_SOURCE_ARTIFACT_VERSION,
228 sourceId: input.source.id,
229 catalogId,
230 generatedAt,
231 revision,
232 snapshot,
233 };
234};
packages/platform/sdk/src/runtime/sources/executor-tools.ts+3−3
@@ -19,7 +19,7 @@ import * as Effect from "effect/Effect"; · 18 unmodified lines
19import * as Cause from "effect/Cause";19import * as Cause from "effect/Cause";
20import * as Exit from "effect/Exit";20import * as Exit from "effect/Exit";
21import * as Schema from "effect/Schema";21import * as Schema from "effect/Schema";
22import type { RuntimeLocalWorkspaceState } from "../local/runtime-context";22import type { RuntimeLocalWorkspaceState } from "../workspace/runtime-context";
23import {23import {
24 type LocalStorageServices,24 type LocalStorageServices,
25 type InstallationStoreShape,25 type InstallationStoreShape,
@@ -27,8 +27,8 @@ import { · 1 unmodified line
27 type WorkspaceConfigStoreShape,27 type WorkspaceConfigStoreShape,
28 type WorkspaceStateStoreShape,28 type WorkspaceStateStoreShape,
29 makeLocalStorageLayer,29 makeLocalStorageLayer,
30} from "../local/storage";30} from "../workspace/storage";
31import { provideOptionalRuntimeLocalWorkspace } from "../local/runtime-context";31import { provideOptionalRuntimeLocalWorkspace } from "../workspace/runtime-context";
32import { runtimeEffectError } from "../effect-errors";32import { runtimeEffectError } from "../effect-errors";
33 33
34/** Run an Effect as a Promise, preserving the original error (not FiberFailure). */34/** Run an Effect as a Promise, preserving the original error (not FiberFailure). */
packages/platform/sdk/src/runtime/sources/source-auth-service.ts+25−18
@@ -54,7 +54,7 @@ import { · 53 unmodified lines
54 getRuntimeLocalWorkspaceOption,54 getRuntimeLocalWorkspaceOption,
55 provideOptionalRuntimeLocalWorkspace,55 provideOptionalRuntimeLocalWorkspace,
56 type RuntimeLocalWorkspaceState,56 type RuntimeLocalWorkspaceState,
57} from "../local/runtime-context";57} from "../workspace/runtime-context";
58import {58import {
59 exchangeMcpOAuthAuthorizationCode,59 exchangeMcpOAuthAuthorizationCode,
60 startMcpOAuthAuthorization,60 startMcpOAuthAuthorization,
@@ -73,12 +73,13 @@ import { · 12 unmodified lines
73 sourceBindingStateFromSource,73 sourceBindingStateFromSource,
74} from "./source-adapters";74} from "./source-adapters";
75import {75import {
76 createDefaultSecretMaterialDeleter,76 type DeleteSecretMaterial,
77 type ResolveSecretMaterial,77 type ResolveSecretMaterial,
78 SecretMaterialDeleterService,
78 SecretMaterialResolverService,79 SecretMaterialResolverService,
79 SecretMaterialStorerService,80 SecretMaterialStorerService,
80 type StoreSecretMaterial,81 type StoreSecretMaterial,
81} from "../local/secret-material-providers";82} from "../workspace/secret-material-providers";
82import {83import {
83 removeAuthLeaseAndSecrets,84 removeAuthLeaseAndSecrets,
84 upsertOauth2AuthorizedUserLeaseFromTokenResponse,85 upsertOauth2AuthorizedUserLeaseFromTokenResponse,
@@ -101,7 +102,7 @@ import { · 16 unmodified lines
101 type RuntimeSourceStore,102 type RuntimeSourceStore,
102 RuntimeSourceStoreService,103 RuntimeSourceStoreService,
103} from "./source-store";104} from "./source-store";
104import type { WorkspaceStorageServices } from "../local/storage";105import type { WorkspaceStorageServices } from "../workspace/storage";
105import { ControlPlaneStore, type ControlPlaneStoreShape } from "../store";106import { ControlPlaneStore, type ControlPlaneStoreShape } from "../store";
106import { runtimeEffectError } from "../effect-errors";107import { runtimeEffectError } from "../effect-errors";
107 108
@@ -994,6 +995,7 @@ const upsertSourceOauthClient = (input: { · 886 unmodified lines
994 source: Source;995 source: Source;
995 oauthClient: SourceOauthClientInput;996 oauthClient: SourceOauthClientInput;
996 storeSecretMaterial: StoreSecretMaterial;997 storeSecretMaterial: StoreSecretMaterial;
998 deleteSecretMaterial: DeleteSecretMaterial;
997}): Effect.Effect<ResolvedSourceOauthClient, Error, never> =>999}): Effect.Effect<ResolvedSourceOauthClient, Error, never> =>
998 Effect.gen(function* () {1000 Effect.gen(function* () {
999 const adapter = getSourceAdapterForSource(input.source);1001 const adapter = getSourceAdapterForSource(input.source);
@@ -1052,10 +1054,7 @@ const upsertSourceOauthClient = (input: { · 52 unmodified lines
1052 || previousClientSecretRef.handle !== clientSecretRef.handle1054 || previousClientSecretRef.handle !== clientSecretRef.handle
1053 )1055 )
1054 ) {1056 ) {
1055 const deleteSecretMaterial = createDefaultSecretMaterialDeleter({1057 yield* input.deleteSecretMaterial(previousClientSecretRef).pipe(
1056 rows: input.rows,
1057 });
1058 yield* deleteSecretMaterial(previousClientSecretRef).pipe(
1059 Effect.either,1058 Effect.either,
1060 Effect.ignore,1059 Effect.ignore,
1061 );1060 );
@@ -1308,6 +1307,7 @@ const upsertProviderAuthGrant = (input: { · 246 unmodified lines
1308 grantedScopes: ReadonlyArray<string>;1307 grantedScopes: ReadonlyArray<string>;
1309 refreshToken: string | null;1308 refreshToken: string | null;
1310 storeSecretMaterial: StoreSecretMaterial;1309 storeSecretMaterial: StoreSecretMaterial;
1310 deleteSecretMaterial: DeleteSecretMaterial;
1311 existingGrant?: import("#schema").ProviderAuthGrant | null;1311 existingGrant?: import("#schema").ProviderAuthGrant | null;
1312}): Effect.Effect<import("#schema").ProviderAuthGrant, Error, never> =>1312}): Effect.Effect<import("#schema").ProviderAuthGrant, Error, never> =>
1313 Effect.gen(function* () {1313 Effect.gen(function* () {
@@ -1356,10 +1356,7 @@ const upsertProviderAuthGrant = (input: { · 42 unmodified lines
1356 || existingGrant.refreshToken.handle !== refreshTokenRef.handle1356 || existingGrant.refreshToken.handle !== refreshTokenRef.handle
1357 )1357 )
1358 ) {1358 ) {
1359 const deleteSecretMaterial = createDefaultSecretMaterialDeleter({1359 yield* input.deleteSecretMaterial(existingGrant.refreshToken).pipe(
1360 rows: input.rows,
1361 });
1362 yield* deleteSecretMaterial(existingGrant.refreshToken).pipe(
1363 Effect.either,1360 Effect.either,
1364 Effect.ignore,1361 Effect.ignore,
1365 );1362 );
@@ -1747,6 +1744,7 @@ const removeProviderAuthGrantInternal = (input: { · 381 unmodified lines
1747 sourceStore: RuntimeSourceStore;1744 sourceStore: RuntimeSourceStore;
1748 workspaceId: WorkspaceId;1745 workspaceId: WorkspaceId;
1749 grantId: ProviderAuthGrant["id"];1746 grantId: ProviderAuthGrant["id"];
1747 deleteSecretMaterial: DeleteSecretMaterial;
1750}): Effect.Effect<boolean, Error, WorkspaceStorageServices> =>1748}): Effect.Effect<boolean, Error, WorkspaceStorageServices> =>
1751 Effect.gen(function* () {1749 Effect.gen(function* () {
1752 const grantOption = yield* input.rows.providerAuthGrants.getById(input.grantId);1750 const grantOption = yield* input.rows.providerAuthGrants.getById(input.grantId);
@@ -1774,7 +1772,7 @@ const removeProviderAuthGrantInternal = (input: { · 21 unmodified lines
1774 if (latestSource === null) {1772 if (latestSource === null) {
1775 yield* removeAuthLeaseAndSecrets(input.rows, {1773 yield* removeAuthLeaseAndSecrets(input.rows, {
1776 authArtifactId: artifact.id,1774 authArtifactId: artifact.id,
1777 });1775 }, input.deleteSecretMaterial);
1778 yield* input.rows.authArtifacts.removeByWorkspaceSourceAndActor({1776 yield* input.rows.authArtifacts.removeByWorkspaceSourceAndActor({
1779 workspaceId: artifact.workspaceId,1777 workspaceId: artifact.workspaceId,
1780 sourceId: artifact.sourceId,1778 sourceId: artifact.sourceId,
@@ -1800,7 +1798,7 @@ const removeProviderAuthGrantInternal = (input: { · 19 unmodified lines
1800 1798
1801 yield* removeProviderAuthGrantSecret(input.rows, {1799 yield* removeProviderAuthGrantSecret(input.rows, {
1802 grant: grantOption.value,1800 grant: grantOption.value,
1803 });1801 }, input.deleteSecretMaterial);
1804 yield* input.rows.providerAuthGrants.removeById(input.grantId);1802 yield* input.rows.providerAuthGrants.removeById(input.grantId);
1805 return true;1803 return true;
1806 });1804 });
@@ -2093,6 +2091,7 @@ const addExecutorHttpSource = (input: { · 286 unmodified lines
2093 sourceCatalogSync: RuntimeSourceCatalogSyncShape;2091 sourceCatalogSync: RuntimeSourceCatalogSyncShape;
2094 sourceInput: ExecutorHttpEndpointSourceInput;2092 sourceInput: ExecutorHttpEndpointSourceInput;
2095 storeSecretMaterial: StoreSecretMaterial;2093 storeSecretMaterial: StoreSecretMaterial;
2094 deleteSecretMaterial: DeleteSecretMaterial;
2096 resolveSecretMaterial: ResolveSecretMaterial;2095 resolveSecretMaterial: ResolveSecretMaterial;
2097 getLocalServerBaseUrl?: () => string | undefined;2096 getLocalServerBaseUrl?: () => string | undefined;
2098 baseUrl?: string | null;2097 baseUrl?: string | null;
@@ -2305,6 +2304,7 @@ const addExecutorGoogleDiscoverySource = (input: { · 206 unmodified lines
2305 sourceCatalogSync: RuntimeSourceCatalogSyncShape;2304 sourceCatalogSync: RuntimeSourceCatalogSyncShape;
2306 sourceInput: Extract<ExecutorAddSourceInput, { kind: "google_discovery" }>;2305 sourceInput: Extract<ExecutorAddSourceInput, { kind: "google_discovery" }>;
2307 storeSecretMaterial: StoreSecretMaterial;2306 storeSecretMaterial: StoreSecretMaterial;
2307 deleteSecretMaterial: DeleteSecretMaterial;
2308 resolveSecretMaterial: ResolveSecretMaterial;2308 resolveSecretMaterial: ResolveSecretMaterial;
2309 getLocalServerBaseUrl?: () => string | undefined;2309 getLocalServerBaseUrl?: () => string | undefined;
2310 baseUrl?: string | null;2310 baseUrl?: string | null;
@@ -2491,6 +2491,7 @@ const addExecutorGoogleDiscoverySource = (input: { · 180 unmodified lines
2491 source: persistedDraft,2491 source: persistedDraft,
2492 oauthClient: input.sourceInput.oauthClient,2492 oauthClient: input.sourceInput.oauthClient,
2493 storeSecretMaterial: input.storeSecretMaterial,2493 storeSecretMaterial: input.storeSecretMaterial,
2494 deleteSecretMaterial: input.deleteSecretMaterial,
2494 });2495 });
2495 }2496 }
2496 2497
@@ -2820,6 +2821,7 @@ type RuntimeSourceAuthDependencies = { · 323 unmodified lines
2820 sourceCatalogSync: RuntimeSourceCatalogSyncShape;2821 sourceCatalogSync: RuntimeSourceCatalogSyncShape;
2821 resolveSecretMaterial: ResolveSecretMaterial;2822 resolveSecretMaterial: ResolveSecretMaterial;
2822 storeSecretMaterial: StoreSecretMaterial;2823 storeSecretMaterial: StoreSecretMaterial;
2824 deleteSecretMaterial: DeleteSecretMaterial;
2823 getLocalServerBaseUrl?: () => string | undefined;2825 getLocalServerBaseUrl?: () => string | undefined;
2824 localWorkspaceState?: RuntimeLocalWorkspaceState;2826 localWorkspaceState?: RuntimeLocalWorkspaceState;
2825};2827};
@@ -2880,6 +2882,7 @@ const createRuntimeSourceConnectionService = ( · 54 unmodified lines
2880 sourceCatalogSync: input.sourceCatalogSync,2882 sourceCatalogSync: input.sourceCatalogSync,
2881 sourceInput,2883 sourceInput,
2882 storeSecretMaterial: input.storeSecretMaterial,2884 storeSecretMaterial: input.storeSecretMaterial,
2885 deleteSecretMaterial: input.deleteSecretMaterial,
2883 resolveSecretMaterial: input.resolveSecretMaterial,2886 resolveSecretMaterial: input.resolveSecretMaterial,
2884 getLocalServerBaseUrl: input.getLocalServerBaseUrl,2887 getLocalServerBaseUrl: input.getLocalServerBaseUrl,
2885 baseUrl: options?.baseUrl,2888 baseUrl: options?.baseUrl,
@@ -2891,6 +2894,7 @@ const createRuntimeSourceConnectionService = ( · 5 unmodified lines
2891 sourceCatalogSync: input.sourceCatalogSync,2894 sourceCatalogSync: input.sourceCatalogSync,
2892 sourceInput,2895 sourceInput,
2893 storeSecretMaterial: input.storeSecretMaterial,2896 storeSecretMaterial: input.storeSecretMaterial,
2897 deleteSecretMaterial: input.deleteSecretMaterial,
2894 resolveSecretMaterial: input.resolveSecretMaterial,2898 resolveSecretMaterial: input.resolveSecretMaterial,
2895 getLocalServerBaseUrl: input.getLocalServerBaseUrl,2899 getLocalServerBaseUrl: input.getLocalServerBaseUrl,
2896 baseUrl: options?.baseUrl,2900 baseUrl: options?.baseUrl,
@@ -3014,10 +3018,7 @@ const createRuntimeSourceConnectionService = ( · 117 unmodified lines
3014 const secretRef = sourceOauthClientSecretRef(oauthClient.value);3018 const secretRef = sourceOauthClientSecretRef(oauthClient.value);
3015 const removed = yield* input.rows.workspaceOauthClients.removeById(oauthClientId);3019 const removed = yield* input.rows.workspaceOauthClients.removeById(oauthClientId);
3016 if (removed && secretRef) {3020 if (removed && secretRef) {
3017 const deleteSecretMaterial = createDefaultSecretMaterialDeleter({3021 yield* input.deleteSecretMaterial(secretRef).pipe(
3018 rows: input.rows,
3019 });
3020 yield* deleteSecretMaterial(secretRef).pipe(
3021 Effect.either,3022 Effect.either,
3022 Effect.ignore,3023 Effect.ignore,
3023 );3024 );
@@ -3034,6 +3035,7 @@ const createRuntimeSourceConnectionService = ( · 10 unmodified lines
3034 sourceStore: input.sourceStore,3035 sourceStore: input.sourceStore,
3035 workspaceId,3036 workspaceId,
3036 grantId,3037 grantId,
3038 deleteSecretMaterial: input.deleteSecretMaterial,
3037 }),3039 }),
3038 ),3040 ),
3039 };3041 };
@@ -3329,6 +3331,7 @@ const createRuntimeSourceOAuthSessionService = ( · 289 unmodified lines
3329 refreshToken: trimOrNull(exchanged.refresh_token),3331 refreshToken: trimOrNull(exchanged.refresh_token),
3330 existingGrant,3332 existingGrant,
3331 storeSecretMaterial: input.storeSecretMaterial,3333 storeSecretMaterial: input.storeSecretMaterial,
3334 deleteSecretMaterial: input.deleteSecretMaterial,
3332 });3335 });
3333 3336
3334 const targets = yield* Effect.forEach(3337 const targets = yield* Effect.forEach(
@@ -3526,6 +3529,8 @@ const createRuntimeSourceOAuthSessionService = ( · 191 unmodified lines
3526 rows: input.rows,3529 rows: input.rows,
3527 artifact: authArtifact.value,3530 artifact: authArtifact.value,
3528 tokenResponse: exchanged,3531 tokenResponse: exchanged,
3532 storeSecretMaterial: input.storeSecretMaterial,
3533 deleteSecretMaterial: input.deleteSecretMaterial,
3529 });3534 });
3530 }3535 }
3531 3536
@@ -3698,6 +3703,7 @@ export const RuntimeSourceAuthServiceLive = (input: { · 166 unmodified lines
3698 const sourceCatalogSync = yield* RuntimeSourceCatalogSyncService;3703 const sourceCatalogSync = yield* RuntimeSourceCatalogSyncService;
3699 const resolveSecretMaterial = yield* SecretMaterialResolverService;3704 const resolveSecretMaterial = yield* SecretMaterialResolverService;
3700 const storeSecretMaterial = yield* SecretMaterialStorerService;3705 const storeSecretMaterial = yield* SecretMaterialStorerService;
3706 const deleteSecretMaterial = yield* SecretMaterialDeleterService;
3701 const runtimeLocalWorkspace = yield* getRuntimeLocalWorkspaceOption();3707 const runtimeLocalWorkspace = yield* getRuntimeLocalWorkspaceOption();
3702 3708
3703 return createRuntimeSourceAuthService({3709 return createRuntimeSourceAuthService({
@@ -3707,6 +3713,7 @@ export const RuntimeSourceAuthServiceLive = (input: { · 3 unmodified lines
3707 sourceCatalogSync,3713 sourceCatalogSync,
3708 resolveSecretMaterial,3714 resolveSecretMaterial,
3709 storeSecretMaterial,3715 storeSecretMaterial,
3716 deleteSecretMaterial,
3710 getLocalServerBaseUrl: input.getLocalServerBaseUrl,3717 getLocalServerBaseUrl: input.getLocalServerBaseUrl,
3711 localWorkspaceState: runtimeLocalWorkspace ?? undefined,3718 localWorkspaceState: runtimeLocalWorkspace ?? undefined,
3712 });3719 });
packages/platform/sdk/src/runtime/sources/source-discovery.ts+0−1
@@ -1 +0,0 @@
1export * from "../../sources/discovery";
packages/platform/sdk/src/runtime/sources/source-inspection.ts+0−1
@@ -1 +0,0 @@
1export * from "../../sources/inspection";
packages/platform/sdk/src/runtime/sources/source-store.test.ts+2−2
@@ -7,9 +7,9 @@ import { · 6 unmodified lines
7 type Source,7 type Source,
8} from "#schema";8} from "#schema";
9 9
10import type { LoadedLocalExecutorConfig } from "../local/config";10import type { LoadedLocalExecutorConfig } from "../workspace-config";
11import { buildLocalSourceRecord } from "./source-store";11import { buildLocalSourceRecord } from "./source-store";
12import type { LocalWorkspaceState } from "../local/workspace-state";12import type { LocalWorkspaceState } from "../workspace-state";
13 13
14const workspaceId = WorkspaceIdSchema.make("ws_source_store");14const workspaceId = WorkspaceIdSchema.make("ws_source_store");
15const sourceId = SourceIdSchema.make("linear");15const sourceId = SourceIdSchema.make("linear");
packages/platform/sdk/src/runtime/sources/source-store.ts+45−17
@@ -7,13 +7,15 @@ import * as Context from "effect/Context"; · 6 unmodified lines
7import * as Effect from "effect/Effect";7import * as Effect from "effect/Effect";
8import * as Layer from "effect/Layer";8import * as Layer from "effect/Layer";
9 9
10import { SourceTypeDeclarationsRefresherService } from "../catalog/source/type-declarations";
10import {11import {
11 SourceArtifactStore,12 SourceArtifactStore,
12 type WorkspaceStorageServices,13 type WorkspaceStorageServices,
13 WorkspaceConfigStore,14 WorkspaceConfigStore,
14 WorkspaceStateStore,15 WorkspaceStateStore,
15} from "../local/storage";16} from "../workspace/storage";
16import { RuntimeLocalWorkspaceService } from "../local/runtime-context";17import { SecretMaterialDeleterService } from "../workspace/secret-material-providers";
18import { RuntimeLocalWorkspaceService } from "../workspace/runtime-context";
17import { ControlPlaneStore, type ControlPlaneStoreShape } from "../store";19import { ControlPlaneStore, type ControlPlaneStoreShape } from "../store";
18import {20import {
19 loadRuntimeSourceStoreDeps,21 loadRuntimeSourceStoreDeps,
@@ -64,7 +66,11 @@ export const loadSourcesInWorkspace = ( · 44 unmodified lines
64 options: {66 options: {
65 actorAccountId?: AccountId | null;67 actorAccountId?: AccountId | null;
66 } = {},68 } = {},
67): Effect.Effect<readonly Source[], Error, WorkspaceStorageServices> =>69): Effect.Effect<
70 readonly Source[],
71 Error,
72 WorkspaceStorageServices | SourceTypeDeclarationsRefresherService
73> =>
68 Effect.flatMap(74 Effect.flatMap(
69 loadRuntimeSourceStoreDeps(rows, workspaceId),75 loadRuntimeSourceStoreDeps(rows, workspaceId),
70 (deps) => loadSourcesInWorkspaceWithDeps(deps, workspaceId, options),76 (deps) => loadSourcesInWorkspaceWithDeps(deps, workspaceId, options),
@@ -79,7 +85,7 @@ export const listLinkedSecretSourcesInWorkspace = ( · 8 unmodified lines
79): Effect.Effect<85): Effect.Effect<
80 Map<string, Array<{ sourceId: string; sourceName: string }>>,86 Map<string, Array<{ sourceId: string; sourceName: string }>>,
81 Error,87 Error,
82 WorkspaceStorageServices88 WorkspaceStorageServices | SourceTypeDeclarationsRefresherService
83> =>89> =>
84 Effect.flatMap(90 Effect.flatMap(
85 loadRuntimeSourceStoreDeps(rows, workspaceId),91 loadRuntimeSourceStoreDeps(rows, workspaceId),
@@ -93,7 +99,11 @@ export const loadSourceById = ( · 7 unmodified lines
93 sourceId: Source["id"];99 sourceId: Source["id"];
94 actorAccountId?: AccountId | null;100 actorAccountId?: AccountId | null;
95 },101 },
96): Effect.Effect<Source, Error, WorkspaceStorageServices> =>102): Effect.Effect<
103 Source,
104 Error,
105 WorkspaceStorageServices | SourceTypeDeclarationsRefresherService
106> =>
97 Effect.flatMap(107 Effect.flatMap(
98 loadRuntimeSourceStoreDeps(rows, input.workspaceId),108 loadRuntimeSourceStoreDeps(rows, input.workspaceId),
99 (deps) => loadSourceByIdWithDeps(deps, input),109 (deps) => loadSourceByIdWithDeps(deps, input),
@@ -105,11 +115,18 @@ export const removeSourceById = ( · 5 unmodified lines
105 workspaceId: WorkspaceId;115 workspaceId: WorkspaceId;
106 sourceId: Source["id"];116 sourceId: Source["id"];
107 },117 },
108): Effect.Effect<boolean, Error, WorkspaceStorageServices> =>118): Effect.Effect<
109 Effect.flatMap(119 boolean,
110 loadRuntimeSourceStoreDeps(rows, input.workspaceId),120 Error,
111 (deps) => removeSourceByIdWithDeps(deps, input),121 | WorkspaceStorageServices
112 );122 | SourceTypeDeclarationsRefresherService
123 | SecretMaterialDeleterService
124> =>
125 Effect.gen(function* () {
126 const deps = yield* loadRuntimeSourceStoreDeps(rows, input.workspaceId);
127 const deleteSecretMaterial = yield* SecretMaterialDeleterService;
128 return yield* removeSourceByIdWithDeps(deps, input, deleteSecretMaterial);
129 });
113 130
114export const persistSource = (131export const persistSource = (
115 rows: ControlPlaneStoreShape,132 rows: ControlPlaneStoreShape,
@@ -117,11 +134,18 @@ export const persistSource = ( · 1 unmodified line
117 options: {134 options: {
118 actorAccountId?: AccountId | null;135 actorAccountId?: AccountId | null;
119 } = {},136 } = {},
120): Effect.Effect<Source, Error, WorkspaceStorageServices> =>137): Effect.Effect<
121 Effect.flatMap(138 Source,
122 loadRuntimeSourceStoreDeps(rows, source.workspaceId),139 Error,
123 (deps) => persistSourceWithDeps(deps, source, options),140 | WorkspaceStorageServices
124 );141 | SourceTypeDeclarationsRefresherService
142 | SecretMaterialDeleterService
143> =>
144 Effect.gen(function* () {
145 const deps = yield* loadRuntimeSourceStoreDeps(rows, source.workspaceId);
146 const deleteSecretMaterial = yield* SecretMaterialDeleterService;
147 return yield* persistSourceWithDeps(deps, source, options, deleteSecretMaterial);
148 });
125 149
126export class RuntimeSourceStoreService extends Context.Tag(150export class RuntimeSourceStoreService extends Context.Tag(
127 "#runtime/RuntimeSourceStoreService",151 "#runtime/RuntimeSourceStoreService",
@@ -135,6 +159,9 @@ export const RuntimeSourceStoreLive = Layer.effect( · 7 unmodified lines
135 const workspaceConfigStore = yield* WorkspaceConfigStore;159 const workspaceConfigStore = yield* WorkspaceConfigStore;
136 const workspaceStateStore = yield* WorkspaceStateStore;160 const workspaceStateStore = yield* WorkspaceStateStore;
137 const sourceArtifactStore = yield* SourceArtifactStore;161 const sourceArtifactStore = yield* SourceArtifactStore;
162 const sourceTypeDeclarationsRefresher =
163 yield* SourceTypeDeclarationsRefresherService;
164 const deleteSecretMaterial = yield* SecretMaterialDeleterService;
138 165
139 const deps: RuntimeSourceStoreDeps = {166 const deps: RuntimeSourceStoreDeps = {
140 rows,167 rows,
@@ -142,6 +169,7 @@ export const RuntimeSourceStoreLive = Layer.effect( · 1 unmodified line
142 workspaceConfigStore,169 workspaceConfigStore,
143 workspaceStateStore,170 workspaceStateStore,
144 sourceArtifactStore,171 sourceArtifactStore,
172 sourceTypeDeclarationsRefresher,
145 };173 };
146 174
147 return RuntimeSourceStoreService.of({175 return RuntimeSourceStoreService.of({
@@ -152,9 +180,9 @@ export const RuntimeSourceStoreLive = Layer.effect( · 4 unmodified lines
152 loadSourceById: (input) =>180 loadSourceById: (input) =>
153 loadSourceByIdWithDeps(deps, input),181 loadSourceByIdWithDeps(deps, input),
154 removeSourceById: (input) =>182 removeSourceById: (input) =>
155 removeSourceByIdWithDeps(deps, input),183 removeSourceByIdWithDeps(deps, input, deleteSecretMaterial),
156 persistSource: (source, options = {}) =>184 persistSource: (source, options = {}) =>
157 persistSourceWithDeps(deps, source, options),185 persistSourceWithDeps(deps, source, options, deleteSecretMaterial),
158 });186 });
159 }),187 }),
160);188);
packages/platform/sdk/src/runtime/sources/source-store/auth.ts+5−5
@@ -11,7 +11,7 @@ import * as Effect from "effect/Effect"; · 10 unmodified lines
11 11
12import { authArtifactSecretMaterialRefs } from "../../auth/auth-artifacts";12import { authArtifactSecretMaterialRefs } from "../../auth/auth-artifacts";
13import { removeAuthLeaseAndSecrets } from "../../auth/auth-leases";13import { removeAuthLeaseAndSecrets } from "../../auth/auth-leases";
14import { createDefaultSecretMaterialDeleter } from "../../local/secret-material-providers";14import type { DeleteSecretMaterial } from "../../workspace/secret-material-providers";
15import type { ControlPlaneStoreShape } from "../../store";15import type { ControlPlaneStoreShape } from "../../store";
16 16
17const secretRefKey = (ref: { providerId: string; handle: string }): string =>17const secretRefKey = (ref: { providerId: string; handle: string }): string =>
@@ -23,13 +23,12 @@ export const cleanupAuthArtifactSecretRefs = ( · 5 unmodified lines
23 previous: AuthArtifact | null;23 previous: AuthArtifact | null;
24 next: AuthArtifact | null;24 next: AuthArtifact | null;
25 },25 },
26 deleteSecretMaterial: DeleteSecretMaterial,
26) =>27) =>
27 Effect.gen(function* () {28 Effect.gen(function* () {
28 if (input.previous === null) {29 if (input.previous === null) {
29 return;30 return;
30 }31 }
31
32 const deleteSecretMaterial = createDefaultSecretMaterialDeleter({ rows });
33 const nextRefKeys = new Set(32 const nextRefKeys = new Set(
34 (input.next === null ? [] : authArtifactSecretMaterialRefs(input.next)).map(33 (input.next === null ? [] : authArtifactSecretMaterialRefs(input.next)).map(
35 secretRefKey,34 secretRefKey,
@@ -97,6 +96,7 @@ export const removeAuthArtifactsForSource = ( · 61 unmodified lines
97 workspaceId: WorkspaceId;96 workspaceId: WorkspaceId;
98 sourceId: Source["id"];97 sourceId: Source["id"];
99 },98 },
99 deleteSecretMaterial: DeleteSecretMaterial,
100) =>100) =>
101 Effect.gen(function* () {101 Effect.gen(function* () {
102 const existingAuthArtifacts = yield* rows.authArtifacts.listByWorkspaceAndSourceId({102 const existingAuthArtifacts = yield* rows.authArtifacts.listByWorkspaceAndSourceId({
@@ -114,7 +114,7 @@ export const removeAuthArtifactsForSource = ( · 11 unmodified lines
114 (artifact) =>114 (artifact) =>
115 removeAuthLeaseAndSecrets(rows, {115 removeAuthLeaseAndSecrets(rows, {
116 authArtifactId: artifact.id,116 authArtifactId: artifact.id,
117 }),117 }, deleteSecretMaterial),
118 { discard: true },118 { discard: true },
119 );119 );
120 120
@@ -124,7 +124,7 @@ export const removeAuthArtifactsForSource = ( · 3 unmodified lines
124 cleanupAuthArtifactSecretRefs(rows, {124 cleanupAuthArtifactSecretRefs(rows, {
125 previous: artifact,125 previous: artifact,
126 next: null,126 next: null,
127 }),127 }, deleteSecretMaterial),
128 { discard: true },128 { discard: true },
129 );129 );
130 130
packages/platform/sdk/src/runtime/sources/source-store/config.ts+3−3
@@ -6,12 +6,12 @@ import type { · 5 unmodified lines
6} from "#schema";6} from "#schema";
7import { SourceIdSchema } from "#schema";7import { SourceIdSchema } from "#schema";
8 8
9import type { LoadedLocalExecutorConfig } from "../../local/config";9import type { LoadedLocalExecutorConfig } from "../../workspace-config";
10import { LocalUnsupportedSourceKindError } from "../../local/errors";10import { LocalUnsupportedSourceKindError } from "../../workspace-errors";
11import {11import {
12 fromConfigSecretProviderId,12 fromConfigSecretProviderId,
13 toConfigSecretProviderId,13 toConfigSecretProviderId,
14} from "../../local/config-secrets";14} from "../../workspace/config-secrets";
15import { getSourceAdapterForSource } from "../source-adapters";15import { getSourceAdapterForSource } from "../source-adapters";
16import { slugify } from "../slug";16import { slugify } from "../slug";
17 17
packages/platform/sdk/src/runtime/sources/source-store/deps.ts+20−27
@@ -1,33 +1,31 @@
1import type { AccountId, WorkspaceId } from "#schema";1import type { AccountId, WorkspaceId } from "#schema";
2import * as Effect from "effect/Effect";2import * as Effect from "effect/Effect";
3 3
4import type {4import type { LoadedLocalExecutorConfig } from "../../workspace-config";
5 LoadedLocalExecutorConfig,5import {
6 ResolvedLocalWorkspaceContext,6 SourceTypeDeclarationsRefresherService,
7} from "../../local/config";7 type SourceTypeDeclarationsRefresherShape,
8} from "../../catalog/source/type-declarations";
8import {9import {
9 LocalExecutorConfigDecodeError,
10 LocalFileSystemError,
11 LocalWorkspaceStateDecodeError,
12 RuntimeLocalWorkspaceMismatchError,10 RuntimeLocalWorkspaceMismatchError,
13 RuntimeLocalWorkspaceUnavailableError,11 RuntimeLocalWorkspaceUnavailableError,
14} from "../../local/errors";12} from "../../workspace-errors";
15import {13import {
16 requireRuntimeLocalWorkspace,14 requireRuntimeLocalWorkspace,
17 type RuntimeLocalWorkspaceState,15 type RuntimeLocalWorkspaceState,
18} from "../../local/runtime-context";16} from "../../workspace/runtime-context";
19import type {17import type {
20 SourceArtifactStoreShape,18 SourceArtifactStoreShape,
21 WorkspaceStorageServices,19 WorkspaceStorageServices,
22 WorkspaceConfigStoreShape,20 WorkspaceConfigStoreShape,
23 WorkspaceStateStoreShape,21 WorkspaceStateStoreShape,
24} from "../../local/storage";22} from "../../workspace/storage";
25import {23import {
26 SourceArtifactStore,24 SourceArtifactStore,
27 WorkspaceConfigStore,25 WorkspaceConfigStore,
28 WorkspaceStateStore,26 WorkspaceStateStore,
29} from "../../local/storage";27} from "../../workspace/storage";
30import type { LocalWorkspaceState } from "../../local/workspace-state";28import type { LocalWorkspaceState } from "../../workspace-state";
31import type { ControlPlaneStoreShape } from "../../store";29import type { ControlPlaneStoreShape } from "../../store";
32 30
33export type RuntimeSourceStoreDeps = {31export type RuntimeSourceStoreDeps = {
@@ -36,10 +34,10 @@ export type RuntimeSourceStoreDeps = { · 2 unmodified lines
36 workspaceConfigStore: WorkspaceConfigStoreShape;34 workspaceConfigStore: WorkspaceConfigStoreShape;
37 workspaceStateStore: WorkspaceStateStoreShape;35 workspaceStateStore: WorkspaceStateStoreShape;
38 sourceArtifactStore: SourceArtifactStoreShape;36 sourceArtifactStore: SourceArtifactStoreShape;
37 sourceTypeDeclarationsRefresher: SourceTypeDeclarationsRefresherShape;
39};38};
40 39
41export type ResolvedSourceStoreWorkspace = {40export type ResolvedSourceStoreWorkspace = {
42 context: ResolvedLocalWorkspaceContext;
43 installation: {41 installation: {
44 workspaceId: WorkspaceId;42 workspaceId: WorkspaceId;
45 accountId: AccountId;43 accountId: AccountId;
@@ -51,6 +49,9 @@ export type ResolvedSourceStoreWorkspace = { · 5 unmodified lines
51 workspaceState: LocalWorkspaceState;49 workspaceState: LocalWorkspaceState;
52};50};
53 51
52export type RuntimeSourceStoreServices =
53 WorkspaceStorageServices | SourceTypeDeclarationsRefresherService;
54
54export const resolveRuntimeLocalWorkspaceFromDeps = (55export const resolveRuntimeLocalWorkspaceFromDeps = (
55 deps: RuntimeSourceStoreDeps,56 deps: RuntimeSourceStoreDeps,
56 workspaceId: WorkspaceId,57 workspaceId: WorkspaceId,
@@ -58,9 +59,6 @@ export const resolveRuntimeLocalWorkspaceFromDeps = ( · 1 unmodified line
58 ResolvedSourceStoreWorkspace,59 ResolvedSourceStoreWorkspace,
59 | RuntimeLocalWorkspaceUnavailableError60 | RuntimeLocalWorkspaceUnavailableError
60 | RuntimeLocalWorkspaceMismatchError61 | RuntimeLocalWorkspaceMismatchError
61 | LocalFileSystemError
62 | LocalExecutorConfigDecodeError
63 | LocalWorkspaceStateDecodeError
64 | Error,62 | Error,
65 never63 never
66> =>64> =>
@@ -73,15 +71,10 @@ export const resolveRuntimeLocalWorkspaceFromDeps = ( · 6 unmodified lines
73 });71 });
74 }72 }
75 73
76 const loadedConfig = yield* deps.workspaceConfigStore.load(74 const loadedConfig = yield* deps.workspaceConfigStore.load();
77 deps.runtimeLocalWorkspace.context,75 const workspaceState = yield* deps.workspaceStateStore.load();
78 );
79 const workspaceState = yield* deps.workspaceStateStore.load(
80 deps.runtimeLocalWorkspace.context,
81 );
82 76
83 return {77 return {
84 context: deps.runtimeLocalWorkspace.context,
85 installation: deps.runtimeLocalWorkspace.installation,78 installation: deps.runtimeLocalWorkspace.installation,
86 workspaceConfigStore: deps.workspaceConfigStore,79 workspaceConfigStore: deps.workspaceConfigStore,
87 workspaceStateStore: deps.workspaceStateStore,80 workspaceStateStore: deps.workspaceStateStore,
@@ -98,17 +91,16 @@ export const loadRuntimeSourceStoreDeps = ( · 10 unmodified lines
98 RuntimeSourceStoreDeps,91 RuntimeSourceStoreDeps,
99 | RuntimeLocalWorkspaceUnavailableError92 | RuntimeLocalWorkspaceUnavailableError
100 | RuntimeLocalWorkspaceMismatchError93 | RuntimeLocalWorkspaceMismatchError
101 | LocalFileSystemError
102 | LocalExecutorConfigDecodeError
103 | LocalWorkspaceStateDecodeError
104 | Error,94 | Error,
105 WorkspaceStorageServices95 RuntimeSourceStoreServices
106> =>96> =>
107 Effect.gen(function* () {97 Effect.gen(function* () {
108 const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(workspaceId);98 const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(workspaceId);
109 const workspaceConfigStore = yield* WorkspaceConfigStore;99 const workspaceConfigStore = yield* WorkspaceConfigStore;
110 const workspaceStateStore = yield* WorkspaceStateStore;100 const workspaceStateStore = yield* WorkspaceStateStore;
111 const sourceArtifactStore = yield* SourceArtifactStore;101 const sourceArtifactStore = yield* SourceArtifactStore;
102 const sourceTypeDeclarationsRefresher =
103 yield* SourceTypeDeclarationsRefresherService;
112 104
113 return {105 return {
114 rows,106 rows,
@@ -116,5 +108,6 @@ export const loadRuntimeSourceStoreDeps = ( · 1 unmodified line
116 workspaceConfigStore,108 workspaceConfigStore,
117 workspaceStateStore,109 workspaceStateStore,
118 sourceArtifactStore,110 sourceArtifactStore,
111 sourceTypeDeclarationsRefresher,
119 };112 };
120 });113 });
packages/platform/sdk/src/runtime/sources/source-store/lifecycle.ts+11−13
@@ -6,7 +6,8 @@ import { · 5 unmodified lines
6 clearProviderGrantOrphanedAt,6 clearProviderGrantOrphanedAt,
7 markProviderGrantOrphanedIfUnused,7 markProviderGrantOrphanedIfUnused,
8} from "../../auth/provider-grant-lifecycle";8} from "../../auth/provider-grant-lifecycle";
9import type { LocalWorkspaceState } from "../../local/workspace-state";9import type { DeleteSecretMaterial } from "../../workspace/secret-material-providers";
10import type { LocalWorkspaceState } from "../../workspace-state";
10import {11import {
11 stableSourceCatalogId,12 stableSourceCatalogId,
12 stableSourceCatalogRevisionId,13 stableSourceCatalogRevisionId,
@@ -39,6 +40,7 @@ export const removeSourceByIdWithDeps = ( · 26 unmodified lines
39 workspaceId: WorkspaceId;40 workspaceId: WorkspaceId;
40 sourceId: Source["id"];41 sourceId: Source["id"];
41 },42 },
43 deleteSecretMaterial: DeleteSecretMaterial,
42): Effect.Effect<boolean, Error, never> =>44): Effect.Effect<boolean, Error, never> =>
43 Effect.gen(function* () {45 Effect.gen(function* () {
44 const localWorkspace = yield* resolveRuntimeLocalWorkspaceFromDeps(46 const localWorkspace = yield* resolveRuntimeLocalWorkspaceFromDeps(
@@ -55,7 +57,6 @@ export const removeSourceByIdWithDeps = ( · 10 unmodified lines
55 };57 };
56 delete sources[input.sourceId];58 delete sources[input.sourceId];
57 yield* localWorkspace.workspaceConfigStore.writeProject({59 yield* localWorkspace.workspaceConfigStore.writeProject({
58 context: localWorkspace.context,
59 config: {60 config: {
60 ...projectConfig,61 ...projectConfig,
61 sources,62 sources,
@@ -69,11 +70,9 @@ export const removeSourceByIdWithDeps = ( · 7 unmodified lines
69 sources: remainingSources,70 sources: remainingSources,
70 };71 };
71 yield* localWorkspace.workspaceStateStore.write({72 yield* localWorkspace.workspaceStateStore.write({
72 context: localWorkspace.context,
73 state: workspaceState,73 state: workspaceState,
74 });74 });
75 yield* localWorkspace.sourceArtifactStore.remove({75 yield* localWorkspace.sourceArtifactStore.remove({
76 context: localWorkspace.context,
77 sourceId: input.sourceId,76 sourceId: input.sourceId,
78 });77 });
79 const existingAuthArtifacts =78 const existingAuthArtifacts =
@@ -91,7 +90,7 @@ export const removeSourceByIdWithDeps = ( · 11 unmodified lines
91 workspaceId: input.workspaceId,90 workspaceId: input.workspaceId,
92 sourceId: input.sourceId,91 sourceId: input.sourceId,
93 });92 });
94 yield* removeAuthArtifactsForSource(deps.rows, input);93 yield* removeAuthArtifactsForSource(deps.rows, input, deleteSecretMaterial);
95 yield* Effect.forEach(94 yield* Effect.forEach(
96 [...removedGrantIds],95 [...removedGrantIds],
97 (grantId) =>96 (grantId) =>
@@ -112,6 +111,7 @@ export const persistSourceWithDeps = ( · 14 unmodified lines
112 options: {111 options: {
113 actorAccountId?: AccountId | null;112 actorAccountId?: AccountId | null;
114 } = {},113 } = {},
114 deleteSecretMaterial: DeleteSecretMaterial,
115): Effect.Effect<Source, Error, never> =>115): Effect.Effect<Source, Error, never> =>
116 Effect.gen(function* () {116 Effect.gen(function* () {
117 const localWorkspace = yield* resolveRuntimeLocalWorkspaceFromDeps(117 const localWorkspace = yield* resolveRuntimeLocalWorkspaceFromDeps(
@@ -155,7 +155,6 @@ export const persistSourceWithDeps = ( · 37 unmodified lines
155 config: localWorkspace.loadedConfig.config,155 config: localWorkspace.loadedConfig.config,
156 });156 });
157 yield* localWorkspace.workspaceConfigStore.writeProject({157 yield* localWorkspace.workspaceConfigStore.writeProject({
158 context: localWorkspace.context,
159 config: {158 config: {
160 ...projectConfig,159 ...projectConfig,
161 sources,160 sources,
@@ -175,7 +174,7 @@ export const persistSourceWithDeps = ( · 13 unmodified lines
175 if (existingRuntimeAuthArtifact !== null) {174 if (existingRuntimeAuthArtifact !== null) {
176 yield* removeAuthLeaseAndSecrets(deps.rows, {175 yield* removeAuthLeaseAndSecrets(deps.rows, {
177 authArtifactId: existingRuntimeAuthArtifact.id,176 authArtifactId: existingRuntimeAuthArtifact.id,
178 });177 }, deleteSecretMaterial);
179 }178 }
180 yield* deps.rows.authArtifacts.removeByWorkspaceSourceAndActor({179 yield* deps.rows.authArtifacts.removeByWorkspaceSourceAndActor({
181 workspaceId: nextSource.workspaceId,180 workspaceId: nextSource.workspaceId,
@@ -191,20 +190,20 @@ export const persistSourceWithDeps = ( · 9 unmodified lines
191 ) {190 ) {
192 yield* removeAuthLeaseAndSecrets(deps.rows, {191 yield* removeAuthLeaseAndSecrets(deps.rows, {
193 authArtifactId: existingRuntimeAuthArtifact.id,192 authArtifactId: existingRuntimeAuthArtifact.id,
194 });193 }, deleteSecretMaterial);
195 }194 }
196 }195 }
197 196
198 yield* cleanupAuthArtifactSecretRefs(deps.rows, {197 yield* cleanupAuthArtifactSecretRefs(deps.rows, {
199 previous: existingRuntimeAuthArtifact ?? null,198 previous: existingRuntimeAuthArtifact ?? null,
200 next: runtimeAuthArtifact,199 next: runtimeAuthArtifact,
201 });200 }, deleteSecretMaterial);
202 201
203 if (importAuthArtifact === null) {202 if (importAuthArtifact === null) {
204 if (existingImportAuthArtifact !== null) {203 if (existingImportAuthArtifact !== null) {
205 yield* removeAuthLeaseAndSecrets(deps.rows, {204 yield* removeAuthLeaseAndSecrets(deps.rows, {
206 authArtifactId: existingImportAuthArtifact.id,205 authArtifactId: existingImportAuthArtifact.id,
207 });206 }, deleteSecretMaterial);
208 }207 }
209 yield* deps.rows.authArtifacts.removeByWorkspaceSourceAndActor({208 yield* deps.rows.authArtifacts.removeByWorkspaceSourceAndActor({
210 workspaceId: nextSource.workspaceId,209 workspaceId: nextSource.workspaceId,
@@ -220,14 +219,14 @@ export const persistSourceWithDeps = ( · 9 unmodified lines
220 ) {219 ) {
221 yield* removeAuthLeaseAndSecrets(deps.rows, {220 yield* removeAuthLeaseAndSecrets(deps.rows, {
222 authArtifactId: existingImportAuthArtifact.id,221 authArtifactId: existingImportAuthArtifact.id,
223 });222 }, deleteSecretMaterial);
224 }223 }
225 }224 }
226 225
227 yield* cleanupAuthArtifactSecretRefs(deps.rows, {226 yield* cleanupAuthArtifactSecretRefs(deps.rows, {
228 previous: existingImportAuthArtifact ?? null,227 previous: existingImportAuthArtifact ?? null,
229 next: importAuthArtifact,228 next: importAuthArtifact,
230 });229 }, deleteSecretMaterial);
231 230
232 const previousGrantIds = providerGrantIdsFromArtifacts([231 const previousGrantIds = providerGrantIdsFromArtifacts([
233 existingRuntimeAuthArtifact,232 existingRuntimeAuthArtifact,
@@ -271,7 +270,6 @@ export const persistSourceWithDeps = ( · 37 unmodified lines
271 },270 },
272 };271 };
273 yield* localWorkspace.workspaceStateStore.write({272 yield* localWorkspace.workspaceStateStore.write({
274 context: localWorkspace.context,
275 state: workspaceState,273 state: workspaceState,
276 });274 });
277 275
packages/platform/sdk/src/runtime/sources/source-store/records.ts+7−24
@@ -10,17 +10,13 @@ import * as Effect from "effect/Effect"; · 9 unmodified lines
10 10
11import { sourceAuthFromAuthArtifact } from "../../auth/auth-artifacts";11import { sourceAuthFromAuthArtifact } from "../../auth/auth-artifacts";
12import { authArtifactSecretMaterialRefs } from "../../auth/auth-artifacts";12import { authArtifactSecretMaterialRefs } from "../../auth/auth-artifacts";
13import { refreshWorkspaceSourceTypeDeclarationsInBackground } from "../../catalog/source/type-declarations";13import type { LoadedLocalExecutorConfig } from "../../workspace-config";
14import type { LoadedLocalExecutorConfig } from "../../local/config";
15import {14import {
16 LocalConfiguredSourceNotFoundError,15 LocalConfiguredSourceNotFoundError,
17 LocalExecutorConfigDecodeError,
18 LocalFileSystemError,
19 LocalWorkspaceStateDecodeError,
20 RuntimeLocalWorkspaceMismatchError,16 RuntimeLocalWorkspaceMismatchError,
21 RuntimeLocalWorkspaceUnavailableError,17 RuntimeLocalWorkspaceUnavailableError,
22} from "../../local/errors";18} from "../../workspace-errors";
23import type { LocalWorkspaceState } from "../../local/workspace-state";19import type { LocalWorkspaceState } from "../../workspace-state";
24import { getSourceAdapter } from "../source-adapters";20import { getSourceAdapter } from "../source-adapters";
25import {21import {
26 resolveRuntimeLocalWorkspaceFromDeps,22 resolveRuntimeLocalWorkspaceFromDeps,
@@ -131,9 +127,6 @@ export const loadSourcesInWorkspaceWithDeps = ( · 104 unmodified lines
131 readonly Source[],127 readonly Source[],
132 | RuntimeLocalWorkspaceUnavailableError128 | RuntimeLocalWorkspaceUnavailableError
133 | RuntimeLocalWorkspaceMismatchError129 | RuntimeLocalWorkspaceMismatchError
134 | LocalFileSystemError
135 | LocalExecutorConfigDecodeError
136 | LocalWorkspaceStateDecodeError
137 | LocalConfiguredSourceNotFoundError130 | LocalConfiguredSourceNotFoundError
138 | Error,131 | Error,
139 never132 never
@@ -191,7 +184,6 @@ export const syncWorkspaceSourceTypeDeclarationsWithDeps = ( · 51 unmodified lines
191 const entries = yield* Effect.forEach(sources, (source) =>184 const entries = yield* Effect.forEach(sources, (source) =>
192 Effect.map(185 Effect.map(
193 deps.sourceArtifactStore.read({186 deps.sourceArtifactStore.read({
194 context: localWorkspace.context,
195 sourceId: source.id,187 sourceId: source.id,
196 }),188 }),
197 (artifact) =>189 (artifact) =>
@@ -204,13 +196,10 @@ export const syncWorkspaceSourceTypeDeclarationsWithDeps = ( · 6 unmodified lines
204 ),196 ),
205 );197 );
206 198
207 yield* Effect.sync(() => {199 yield* deps.sourceTypeDeclarationsRefresher.refreshWorkspaceInBackground({
208 refreshWorkspaceSourceTypeDeclarationsInBackground({200 entries: entries.filter(
209 context: localWorkspace.context,201 (entry): entry is NonNullable<typeof entry> => entry !== null,
210 entries: entries.filter(202 ),
211 (entry): entry is NonNullable<typeof entry> => entry !== null,
212 ),
213 });
214 });203 });
215 }).pipe(204 }).pipe(
216 Effect.withSpan("source.types.refresh_workspace.schedule", {205 Effect.withSpan("source.types.refresh_workspace.schedule", {
@@ -236,9 +225,6 @@ export const listLinkedSecretSourcesInWorkspaceWithDeps = ( · 19 unmodified lines
236 Map<string, Array<{ sourceId: string; sourceName: string }>>,225 Map<string, Array<{ sourceId: string; sourceName: string }>>,
237 | RuntimeLocalWorkspaceUnavailableError226 | RuntimeLocalWorkspaceUnavailableError
238 | RuntimeLocalWorkspaceMismatchError227 | RuntimeLocalWorkspaceMismatchError
239 | LocalFileSystemError
240 | LocalExecutorConfigDecodeError
241 | LocalWorkspaceStateDecodeError
242 | LocalConfiguredSourceNotFoundError228 | LocalConfiguredSourceNotFoundError
243 | Error,229 | Error,
244 never230 never
@@ -295,9 +281,6 @@ export const loadSourceByIdWithDeps = ( · 50 unmodified lines
295 Source,281 Source,
296 | RuntimeLocalWorkspaceUnavailableError282 | RuntimeLocalWorkspaceUnavailableError
297 | RuntimeLocalWorkspaceMismatchError283 | RuntimeLocalWorkspaceMismatchError
298 | LocalFileSystemError
299 | LocalExecutorConfigDecodeError
300 | LocalWorkspaceStateDecodeError
301 | LocalConfiguredSourceNotFoundError284 | LocalConfiguredSourceNotFoundError
302 | Error,285 | Error,
303 never286 never
packages/platform/sdk/src/runtime/workspace-config.ts+9−0
@@ -0,0 +1,9 @@
1import type { LocalExecutorConfig } from "#schema";
2
3export type LoadedExecutorConfig = {
4 config: LocalExecutorConfig | null;
5 homeConfig: LocalExecutorConfig | null;
6 projectConfig: LocalExecutorConfig | null;
7};
8
9export type LoadedLocalExecutorConfig = LoadedExecutorConfig;
packages/platform/sdk/src/runtime/workspace-errors.ts+36−0
@@ -0,0 +1,36 @@
1import * as Data from "effect/Data";
2
3export class RuntimeLocalWorkspaceUnavailableError extends Data.TaggedError(
4 "RuntimeLocalWorkspaceUnavailableError",
5)<{
6 readonly message: string;
7}> {}
8
9export class RuntimeLocalWorkspaceMismatchError extends Data.TaggedError(
10 "RuntimeLocalWorkspaceMismatchError",
11)<{
12 readonly message: string;
13 readonly requestedWorkspaceId: string;
14 readonly activeWorkspaceId: string;
15}> {}
16
17export class LocalConfiguredSourceNotFoundError extends Data.TaggedError(
18 "LocalConfiguredSourceNotFoundError",
19)<{
20 readonly message: string;
21 readonly sourceId: string;
22}> {}
23
24export class LocalSourceArtifactMissingError extends Data.TaggedError(
25 "LocalSourceArtifactMissingError",
26)<{
27 readonly message: string;
28 readonly sourceId: string;
29}> {}
30
31export class LocalUnsupportedSourceKindError extends Data.TaggedError(
32 "LocalUnsupportedSourceKindError",
33)<{
34 readonly message: string;
35 readonly kind: string;
36}> {}
packages/platform/sdk/src/runtime/workspace-state.ts+42−0
@@ -0,0 +1,42 @@
1import {
2 PolicyIdSchema,
3 SourceStatusSchema,
4 TimestampMsSchema,
5} from "#schema";
6import * as Schema from "effect/Schema";
7
8const LocalWorkspaceSourceStateSchema = Schema.Struct({
9 status: SourceStatusSchema,
10 lastError: Schema.NullOr(Schema.String),
11 sourceHash: Schema.NullOr(Schema.String),
12 createdAt: TimestampMsSchema,
13 updatedAt: TimestampMsSchema,
14});
15
16const LocalWorkspacePolicyStateSchema = Schema.Struct({
17 id: PolicyIdSchema,
18 createdAt: TimestampMsSchema,
19 updatedAt: TimestampMsSchema,
20});
21
22export const LocalWorkspaceStateSchema = Schema.Struct({
23 version: Schema.Literal(1),
24 sources: Schema.Record({
25 key: Schema.String,
26 value: LocalWorkspaceSourceStateSchema,
27 }),
28 policies: Schema.Record({
29 key: Schema.String,
30 value: LocalWorkspacePolicyStateSchema,
31 }),
32});
33
34export type LocalWorkspaceSourceState = typeof LocalWorkspaceSourceStateSchema.Type;
35export type LocalWorkspacePolicyState = typeof LocalWorkspacePolicyStateSchema.Type;
36export type LocalWorkspaceState = typeof LocalWorkspaceStateSchema.Type;
37
38export const defaultLocalWorkspaceState = (): LocalWorkspaceState => ({
39 version: 1,
40 sources: {},
41 policies: {},
42});
packages/platform/sdk/src/runtime/workspace/config-secrets.tsrenamed from packages/platform/sdk/src/runtime/local/config-secrets.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/workspace/runtime-context.tsrenamed from packages/platform/sdk/src/runtime/local/runtime-context.ts+4−6
@@ -4,17 +4,15 @@ import * as Layer from "effect/Layer"; · 3 unmodified lines
4import * as Option from "effect/Option";4import * as Option from "effect/Option";
5 5
6import type { AccountId, WorkspaceId } from "#schema";6import type { AccountId, WorkspaceId } from "#schema";
7import type {7import type { ExecutorWorkspaceContext } from "../../workspace";
8 LoadedLocalExecutorConfig,8import type { LoadedLocalExecutorConfig } from "../workspace-config";
9 ResolvedLocalWorkspaceContext,
10} from "./config";
11import {9import {
12 RuntimeLocalWorkspaceMismatchError,10 RuntimeLocalWorkspaceMismatchError,
13 RuntimeLocalWorkspaceUnavailableError,11 RuntimeLocalWorkspaceUnavailableError,
14} from "./errors";12} from "../workspace-errors";
15 13
16export type RuntimeLocalWorkspaceState = {14export type RuntimeLocalWorkspaceState = {
17 context: ResolvedLocalWorkspaceContext;15 workspace: ExecutorWorkspaceContext;
18 installation: {16 installation: {
19 workspaceId: WorkspaceId;17 workspaceId: WorkspaceId;
20 accountId: AccountId;18 accountId: AccountId;
packages/platform/sdk/src/runtime/workspace/secret-material-providers.ts+63−0
@@ -0,0 +1,63 @@
1import type { InstanceConfig } from "../../local/contracts";
2import * as Context from "effect/Context";
3import * as Effect from "effect/Effect";
4
5import type {
6 SecretMaterialPurpose,
7 SecretRef,
8} from "#schema";
9
10export type SecretMaterialResolveContext = {
11 params?: Readonly<Record<string, string | undefined>>;
12};
13
14export type ResolveSecretMaterial = (input: {
15 ref: SecretRef;
16 context?: SecretMaterialResolveContext;
17}) => Effect.Effect<string, Error, never>;
18
19export type StoreSecretMaterial = (input: {
20 purpose: SecretMaterialPurpose;
21 value: string;
22 name?: string | null;
23 providerId?: string;
24}) => Effect.Effect<SecretRef, Error, never>;
25
26export type DeleteSecretMaterial = (
27 ref: SecretRef,
28) => Effect.Effect<boolean, Error, never>;
29
30export type UpdateSecretMaterial = (input: {
31 ref: SecretRef;
32 name?: string | null;
33 value?: string;
34}) => Effect.Effect<{
35 id: string;
36 providerId: string;
37 name: string | null;
38 purpose: string;
39 createdAt: number;
40 updatedAt: number;
41}, Error, never>;
42
43export type ResolveInstanceConfig = () => Effect.Effect<InstanceConfig, Error, never>;
44
45export class SecretMaterialResolverService extends Context.Tag(
46 "#runtime/SecretMaterialResolverService",
47)<SecretMaterialResolverService, ResolveSecretMaterial>() {}
48
49export class SecretMaterialStorerService extends Context.Tag(
50 "#runtime/SecretMaterialStorerService",
51)<SecretMaterialStorerService, StoreSecretMaterial>() {}
52
53export class SecretMaterialDeleterService extends Context.Tag(
54 "#runtime/SecretMaterialDeleterService",
55)<SecretMaterialDeleterService, DeleteSecretMaterial>() {}
56
57export class SecretMaterialUpdaterService extends Context.Tag(
58 "#runtime/SecretMaterialUpdaterService",
59)<SecretMaterialUpdaterService, UpdateSecretMaterial>() {}
60
61export class LocalInstanceConfigService extends Context.Tag(
62 "#runtime/LocalInstanceConfigService",
63)<LocalInstanceConfigService, ResolveInstanceConfig>() {}
packages/platform/sdk/src/runtime/workspace/storage.ts+95−0
@@ -0,0 +1,95 @@
1import type { LocalInstallation, LocalExecutorConfig } from "#schema";
2import * as Context from "effect/Context";
3import * as Layer from "effect/Layer";
4
5import type { LoadedLocalExecutorConfig } from "../workspace-config";
6import type { LocalSourceArtifact } from "../source-artifacts";
7import type { LocalWorkspaceState } from "../workspace-state";
8import type { SourceCatalogSyncResult } from "@executor/source-core";
9import type { Source } from "#schema";
10
11export type InstallationStoreShape = {
12 load: () => import("effect/Effect").Effect<LocalInstallation, Error, never>;
13 getOrProvision: () => import("effect/Effect").Effect<LocalInstallation, Error, never>;
14};
15
16export class InstallationStore extends Context.Tag(
17 "#runtime/InstallationStore",
18)<InstallationStore, InstallationStoreShape>() {}
19
20export type WorkspaceConfigStoreShape = {
21 load: () => import("effect/Effect").Effect<LoadedLocalExecutorConfig, Error, never>;
22 writeProject: (input: {
23 config: LocalExecutorConfig;
24 }) => import("effect/Effect").Effect<void, Error, never>;
25 resolveRelativePath: (input: { path: string; workspaceRoot: string }) => string;
26};
27
28export class WorkspaceConfigStore extends Context.Tag(
29 "#runtime/WorkspaceConfigStore",
30)<WorkspaceConfigStore, WorkspaceConfigStoreShape>() {}
31
32export type WorkspaceStateStoreShape = {
33 load: () => import("effect/Effect").Effect<LocalWorkspaceState, Error, never>;
34 write: (input: {
35 state: LocalWorkspaceState;
36 }) => import("effect/Effect").Effect<void, Error, never>;
37};
38
39export class WorkspaceStateStore extends Context.Tag(
40 "#runtime/WorkspaceStateStore",
41)<WorkspaceStateStore, WorkspaceStateStoreShape>() {}
42
43export type SourceArtifactStoreShape = {
44 build: (input: {
45 source: Source;
46 syncResult: SourceCatalogSyncResult;
47 }) => LocalSourceArtifact;
48 read: (input: {
49 sourceId: string;
50 }) => import("effect/Effect").Effect<LocalSourceArtifact | null, Error, never>;
51 write: (input: {
52 sourceId: string;
53 artifact: LocalSourceArtifact;
54 }) => import("effect/Effect").Effect<void, Error, never>;
55 remove: (input: {
56 sourceId: string;
57 }) => import("effect/Effect").Effect<void, Error, never>;
58};
59
60export class SourceArtifactStore extends Context.Tag(
61 "#runtime/SourceArtifactStore",
62)<SourceArtifactStore, SourceArtifactStoreShape>() {}
63
64export type LocalStorageServices =
65 | InstallationStore
66 | WorkspaceConfigStore
67 | WorkspaceStateStore
68 | SourceArtifactStore;
69
70export type WorkspaceStorageServices =
71 | WorkspaceConfigStore
72 | WorkspaceStateStore
73 | SourceArtifactStore;
74
75export const makeWorkspaceStorageLayer = (input: {
76 workspaceConfigStore: WorkspaceConfigStoreShape;
77 workspaceStateStore: WorkspaceStateStoreShape;
78 sourceArtifactStore: SourceArtifactStoreShape;
79}) =>
80 Layer.mergeAll(
81 Layer.succeed(WorkspaceConfigStore, input.workspaceConfigStore),
82 Layer.succeed(WorkspaceStateStore, input.workspaceStateStore),
83 Layer.succeed(SourceArtifactStore, input.sourceArtifactStore),
84 );
85
86export const makeLocalStorageLayer = (input: {
87 installationStore: InstallationStoreShape;
88 workspaceConfigStore: WorkspaceConfigStoreShape;
89 workspaceStateStore: WorkspaceStateStoreShape;
90 sourceArtifactStore: SourceArtifactStoreShape;
91}) =>
92 Layer.mergeAll(
93 Layer.succeed(InstallationStore, input.installationStore),
94 makeWorkspaceStorageLayer(input),
95 );
packages/platform/sdk/src/runtime/workspace/workspace-sync.tsrenamed from packages/platform/sdk/src/runtime/local/workspace-sync.ts+8−18
@@ -1,15 +1,8 @@
1import { FileSystem } from "@effect/platform";
2import * as Effect from "effect/Effect";1import * as Effect from "effect/Effect";
3 2
4import {3import { type LoadedLocalExecutorConfig } from "../workspace-config";
5 type LoadedLocalExecutorConfig,4import { WorkspaceStateStore } from "./storage";
6 type ResolvedLocalWorkspaceContext,5import { type LocalWorkspaceState } from "../workspace-state";
7} from "./config";
8import {
9 loadLocalWorkspaceState,
10 writeLocalWorkspaceState,
11 type LocalWorkspaceState,
12} from "./workspace-state";
13 6
14const trimOrNull = (value: string | null | undefined): string | null => {7const trimOrNull = (value: string | null | undefined): string | null => {
15 if (value == null) {8 if (value == null) {
@@ -41,11 +34,11 @@ export const derivePolicyConfigKey = ( · 25 unmodified lines
41};34};
42 35
43const pruneLocalWorkspaceState = (input: {36const pruneLocalWorkspaceState = (input: {
44 context: ResolvedLocalWorkspaceContext;
45 loadedConfig: LoadedLocalExecutorConfig;37 loadedConfig: LoadedLocalExecutorConfig;
46}): Effect.Effect<LocalWorkspaceState, Error, FileSystem.FileSystem> =>38}): Effect.Effect<LocalWorkspaceState, Error, WorkspaceStateStore> =>
47 Effect.gen(function* () {39 Effect.gen(function* () {
48 const currentState = yield* loadLocalWorkspaceState(input.context);40 const workspaceStateStore = yield* WorkspaceStateStore;
41 const currentState = yield* workspaceStateStore.load();
49 42
50 const configuredSourceIds = new Set(43 const configuredSourceIds = new Set(
51 Object.keys(input.loadedConfig.config?.sources ?? {}),44 Object.keys(input.loadedConfig.config?.sources ?? {}),
@@ -72,8 +65,7 @@ const pruneLocalWorkspaceState = (input: { · 20 unmodified lines
72 return currentState;65 return currentState;
73 }66 }
74 67
75 yield* writeLocalWorkspaceState({68 yield* workspaceStateStore.write({
76 context: input.context,
77 state: nextState,69 state: nextState,
78 });70 });
79 71
@@ -81,12 +73,10 @@ const pruneLocalWorkspaceState = (input: { · 1 unmodified line
81 });73 });
82 74
83export const synchronizeLocalWorkspaceState = (input: {75export const synchronizeLocalWorkspaceState = (input: {
84 context: ResolvedLocalWorkspaceContext;
85 loadedConfig: LoadedLocalExecutorConfig;76 loadedConfig: LoadedLocalExecutorConfig;
86}): Effect.Effect<LoadedLocalExecutorConfig["config"], Error, FileSystem.FileSystem> =>77}): Effect.Effect<LoadedLocalExecutorConfig["config"], Error, WorkspaceStateStore> =>
87 Effect.gen(function* () {78 Effect.gen(function* () {
88 yield* pruneLocalWorkspaceState({79 yield* pruneLocalWorkspaceState({
89 context: input.context,
90 loadedConfig: input.loadedConfig,80 loadedConfig: input.loadedConfig,
91 });81 });
92 82
packages/platform/sdk/src/sources/inspection.ts+1−1
@@ -16,7 +16,7 @@ import { · 15 unmodified lines
16} from "../errors";16} from "../errors";
17import * as Effect from "effect/Effect";17import * as Effect from "effect/Effect";
18 18
19import { LocalSourceArtifactMissingError } from "../runtime/local/errors";19import { LocalSourceArtifactMissingError } from "../runtime/workspace-errors";
20import { operationErrors } from "../runtime/policy/operation-errors";20import { operationErrors } from "../runtime/policy/operation-errors";
21import {21import {
22 buildLoadedSourceCatalogToolContract,22 buildLoadedSourceCatalogToolContract,
packages/platform/sdk/src/workspace.ts+15−0
@@ -0,0 +1,15 @@
1import type {
2 AccountId,
3 WorkspaceId,
4} from "./schema";
5
6export type ExecutorWorkspaceDescriptor = {
7 workspaceName: string;
8 workspaceRoot?: string | null;
9 metadata?: Readonly<Record<string, unknown>>;
10};
11
12export type ExecutorWorkspaceContext = ExecutorWorkspaceDescriptor & {
13 workspaceId: WorkspaceId;
14 accountId: AccountId;
15};