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
2222 controlPlaneOpenApiSpec,
2323 } from "@executor/platform-api";
2424 import {
25− buildLocalSourceArtifact,
2625 catalogSyncResultFromMcpManifest,
27− deriveLocalInstallation,
26+ type ResolveExecutionEnvironment,
27+} from "@executor/platform-sdk/runtime";
28+import {
29+ buildLocalSourceArtifact,
30+ getOrProvisionLocalInstallation,
2831 resolveLocalWorkspaceContext,
2932 writeLocalSourceArtifact,
3033 writeProjectLocalExecutorConfig,
31− type ResolveExecutionEnvironment,
32−} from "@executor/platform-sdk/runtime";
34+} from "@executor/platform-sdk-file";
3335 import { SourceIdSchema } from "@executor/platform-sdk/schema";
3436 import { makeSesExecutor } from "@executor/runtime-ses";
3537 import { Client } from "@modelcontextprotocol/sdk/client/index.js";
@@ -97,7 +99,7 @@ const writeConfiguredLocalMcpSource = (input: { · 61 unmodified lines
9799 const context = yield* resolveLocalWorkspaceContext({
98100 workspaceRoot: input.workspaceRoot,
99101 });
100− const installation = deriveLocalInstallation(context);
102+ const installation = yield* getOrProvisionLocalInstallation({ context });
101103
102104 yield* writeProjectLocalExecutorConfig({
103105 context,
bun.lock+28−0
@@ -30,6 +30,7 @@ · 29 unmodified lines
3030 "@executor/platform-api": "workspace:*",
3131 "@executor/platform-internal": "workspace:*",
3232 "@executor/platform-sdk": "workspace:*",
33+ "@executor/platform-sdk-file": "workspace:*",
3334 "@executor/server": "workspace:*",
3435 "effect": "catalog:",
3536 },
@@ -136,6 +137,7 @@ · 100 unmodified lines
136137 "@effect/platform": "catalog:",
137138 "@effect/platform-node": "catalog:",
138139 "@effect/vitest": "catalog:",
140+ "@executor/platform-sdk-file": "workspace:*",
139141 "@types/jsdom": "^21.1.7",
140142 "@types/node": "catalog:",
141143 "@types/react": "^19.2.10",
@@ -332,6 +334,29 @@ · 190 unmodified lines
332334 "zod": "catalog:",
333335 },
334336 },
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+ },
335360 "packages/platform/server": {
336361 "name": "@executor/server",
337362 "version": "0.0.0",
@@ -343,6 +368,7 @@ · 5 unmodified lines
343368 "@executor/platform-api": "workspace:*",
344369 "@executor/platform-internal": "workspace:*",
345370 "@executor/platform-sdk": "workspace:*",
371+ "@executor/platform-sdk-file": "workspace:*",
346372 "@opentelemetry/api": "^1.9.0",
347373 "@opentelemetry/exporter-trace-otlp-grpc": "^0.213.0",
348374 "@opentelemetry/resources": "^2.6.0",
@@ -684,6 +710,8 @@ · 335 unmodified lines
684710
685711 "@executor/platform-sdk": ["@executor/platform-sdk@workspace:packages/platform/sdk"],
686712
713+ "@executor/platform-sdk-file": ["@executor/platform-sdk-file@workspace:packages/platform/sdk-file"],
714+
687715 "@executor/react": ["@executor/react@workspace:packages/clients/react"],
688716
689717 "@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
2020
2121 import {
2222 ControlPlaneStore,
23+ LocalInstanceConfigService,
24+ SecretMaterialDeleterService,
25+ SecretMaterialStorerService,
26+ SecretMaterialUpdaterService,
2327 type ControlPlaneStoreShape,
2428 type WorkspaceInternalToolContext,
25− RuntimeLocalWorkspaceState,
2629 RuntimeSourceAuthService,
2730 RuntimeSourceCatalogSyncService,
2831 RuntimeSourceStore,
2932 RuntimeSourceStoreService,
33+} from "@executor/platform-sdk/runtime";
34+import {
35+ type RuntimeLocalWorkspaceState,
36+ provideOptionalRuntimeLocalWorkspace,
37+} from "../../../sdk/src/runtime/workspace/runtime-context";
38+import {
3039 SourceArtifactStore,
3140 type SourceArtifactStoreShape,
3241 WorkspaceConfigStore,
@@ -34,8 +43,7 @@ import { · 1 unmodified line
3443 WorkspaceStateStore,
3544 type WorkspaceStateStoreShape,
3645 makeWorkspaceStorageLayer,
37− provideOptionalRuntimeLocalWorkspace,
38−} from "@executor/platform-sdk/runtime";
46+} from "../../../sdk/src/runtime/workspace/storage";
3947 import {
4048 CreateSecretPayloadSchema,
4149 CreateSecretResultSchema,
@@ -53,7 +61,6 @@ import { · 11 unmodified lines
5361 deleteLocalSecret,
5462 discoverSource,
5563 discoverSourceInspectionTools,
56− getLocalInstanceConfig,
5764 getPolicy,
5865 getSource,
5966 getSourceInspection,
@@ -201,6 +208,10 @@ const makeRuntimeLayer = (input: { · 141 unmodified lines
201208 workspaceConfigStore: WorkspaceConfigStoreShape;
202209 workspaceStateStore: WorkspaceStateStoreShape;
203210 sourceArtifactStore: SourceArtifactStoreShape;
211+ instanceConfigResolver: WorkspaceInternalToolContext["instanceConfigResolver"];
212+ storeSecretMaterial: WorkspaceInternalToolContext["storeSecretMaterial"];
213+ deleteSecretMaterial: WorkspaceInternalToolContext["deleteSecretMaterial"];
214+ updateSecretMaterial: WorkspaceInternalToolContext["updateSecretMaterial"];
204215 }) =>
205216 Layer.mergeAll(
206217 Layer.succeed(ControlPlaneStore, input.controlPlaneStore),
@@ -209,6 +220,10 @@ const makeRuntimeLayer = (input: { · 2 unmodified lines
209220 RuntimeSourceCatalogSyncService,
210221 input.sourceCatalogSyncService,
211222 ),
223+ Layer.succeed(LocalInstanceConfigService, input.instanceConfigResolver),
224+ Layer.succeed(SecretMaterialStorerService, input.storeSecretMaterial),
225+ Layer.succeed(SecretMaterialDeleterService, input.deleteSecretMaterial),
226+ Layer.succeed(SecretMaterialUpdaterService, input.updateSecretMaterial),
212227 makeWorkspaceStorageLayer({
213228 workspaceConfigStore: input.workspaceConfigStore,
214229 workspaceStateStore: input.workspaceStateStore,
@@ -220,8 +235,12 @@ const runRuntimeEffect = <A, E, R>(input: { · 5 unmodified lines
220235 effect: Effect.Effect<A, E, R>;
221236 runtimeLayer: Layer.Layer<
222237 | ControlPlaneStore
238+ | LocalInstanceConfigService
223239 | RuntimeSourceStoreService
224240 | RuntimeSourceCatalogSyncService
241+ | SecretMaterialStorerService
242+ | SecretMaterialDeleterService
243+ | SecretMaterialUpdaterService
225244 | WorkspaceConfigStore
226245 | WorkspaceStateStore
227246 | SourceArtifactStore,
@@ -263,6 +282,10 @@ export const createWorkspaceExecutorAdminToolMap = ( · 35 unmodified lines
263282 workspaceConfigStore: input.workspaceConfigStore,
264283 workspaceStateStore: input.workspaceStateStore,
265284 sourceArtifactStore: input.sourceArtifactStore,
285+ instanceConfigResolver: input.instanceConfigResolver,
286+ storeSecretMaterial: input.storeSecretMaterial,
287+ deleteSecretMaterial: input.deleteSecretMaterial,
288+ updateSecretMaterial: input.updateSecretMaterial,
266289 });
267290 const workspaceStorageLayer = makeWorkspaceStorageLayer({
268291 workspaceConfigStore: input.workspaceConfigStore,
@@ -295,7 +318,7 @@ export const createWorkspaceExecutorAdminToolMap = ( · 26 unmodified lines
295318 "Get local instance config such as supported secret providers.",
296319 inputSchema: emptyInputSchema,
297320 outputSchema: instanceConfigOutputSchema,
298− execute: () => Effect.runPromise(getLocalInstanceConfig()),
321+ execute: () => Effect.runPromise(input.instanceConfigResolver()),
299322 },
300323 metadata,
301324 }),
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
66 import * as Effect from "effect/Effect";
77 import * as Exit from "effect/Exit";
88
9−import type { LocalExecutorConfig } from "#schema";
9+import type { LocalExecutorConfig } from "@executor/platform-sdk/schema";
1010
1111 import {
1212 readOptionalLocalExecutorConfig,
@@ -37,7 +37,7 @@ type CaptureReleaseWorkspaceFixtureArgs = { · 24 unmodified lines
3737 };
3838
3939 const usage = `Usage:
40− bun run ./src/runtime/local/capture-release-workspace-fixture.ts \\
40+ bun run ./src/capture-release-workspace-fixture.ts \\
4141 --workspace-root /path/to/workspace \\
4242 --source-id google-calendar \\
4343 --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
5858 "https://api.github.com",
5959 );
6060 expect(loaded.config?.runtime).toBe("ses");
61− expect(context.homeStateDirectory).toContain("executor");
61+ expect(context.homeStateDirectory.toLowerCase()).toContain("executor");
6262 }).pipe(Effect.provide(NodeFileSystem.layer)),
6363 );
6464
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
1313 type LocalConfigPolicy,
1414 type LocalConfigSecretProvider,
1515 type LocalConfigSource,
16−} from "#schema";
16+} from "@executor/platform-sdk/schema";
1717 import * as Effect from "effect/Effect";
1818 import * as Schema from "effect/Schema";
19+import type { ExecutorWorkspaceDescriptor } from "@executor/platform-sdk";
20+import type { LoadedLocalExecutorConfig } from "../../sdk/src/runtime/workspace-config";
1921 import {
2022 LocalExecutorConfigDecodeError,
2123 LocalFileSystemError,
@@ -24,6 +26,33 @@ import { · 2 unmodified lines
2426
2527 const decodeLocalExecutorConfig = Schema.decodeUnknownSync(LocalExecutorConfigSchema);
2628
29+export type FileLoadedExecutorConfig = LoadedLocalExecutorConfig & {
30+ homeConfigPath: string;
31+ projectConfigPath: string;
32+};
33+
34+export const encodeLocalExecutorConfig = (config: LocalExecutorConfig): string =>
35+ `${JSON.stringify(config, null, 2)}\n`;
36+
37+export 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+
54+export type { LoadedLocalExecutorConfig } from "../../sdk/src/runtime/workspace-config";
55+
2756 const PROJECT_CONFIG_BASENAME = "executor.jsonc";
2857 const PROJECT_CONFIG_DIRECTORY = ".executor";
2958 const EXECUTOR_CONFIG_DIR_ENV = "EXECUTOR_CONFIG_DIR";
@@ -329,14 +358,6 @@ export type ResolvedLocalWorkspaceContext = { · 299 unmodified lines
329358 stateDirectory: string;
330359 };
331360
332−export type LoadedLocalExecutorConfig = {
333− config: LocalExecutorConfig | null;
334− homeConfig: LocalExecutorConfig | null;
335− projectConfig: LocalExecutorConfig | null;
336− homeConfigPath: string;
337− projectConfigPath: string;
338−};
339−
340361 export const resolveLocalWorkspaceContext = (input: {
341362 cwd?: string;
342363 workspaceRoot?: string;
@@ -409,7 +430,7 @@ export const readOptionalLocalExecutorConfig = ( · 66 unmodified lines
409430 export const loadLocalExecutorConfig = (
410431 context: ResolvedLocalWorkspaceContext,
411432 ): Effect.Effect<
412− LoadedLocalExecutorConfig,
433+ FileLoadedExecutorConfig,
413434 LocalFileSystemError | LocalExecutorConfigDecodeError,
414435 FileSystem.FileSystem
415436 > =>
@@ -428,9 +449,6 @@ export const loadLocalExecutorConfig = ( · 12 unmodified lines
428449 };
429450 });
430451
431−export const encodeLocalExecutorConfig = (config: LocalExecutorConfig): string =>
432− `${JSON.stringify(config, null, 2)}\n`;
433−
434452 export const writeProjectLocalExecutorConfig = (input: {
435453 context: ResolvedLocalWorkspaceContext;
436454 config: LocalExecutorConfig;
@@ -448,22 +466,5 @@ export const writeProjectLocalExecutorConfig = (input: { · 11 unmodified lines
448466 );
449467 });
450468
451−export 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−
468469 export const defaultWorkspaceDisplayName = (context: ResolvedLocalWorkspaceContext): string =>
469470 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
2424 WorkspaceOauthClientSchema,
2525 type WorkspaceSourceOauthClient,
2626 WorkspaceSourceOauthClientSchema,
27−} from "#schema";
27+} from "@executor/platform-sdk/schema";
2828 import * as Effect from "effect/Effect";
2929 import * as Option from "effect/Option";
3030 import * 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 @@
1+import { FileSystem } from "@effect/platform";
2+import { NodeFileSystem } from "@effect/platform-node";
3+import {
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";
13+import type {
14+ LocalExecutorConfig,
15+ LocalInstallation,
16+} from "@executor/platform-sdk/schema";
17+import {
18+ type RuntimeControlPlaneOptions,
19+ type ControlPlaneRuntime,
20+ type ResolveSecretMaterial,
21+ createControlPlaneRuntimeFromServices,
22+} from "@executor/platform-sdk/runtime";
23+import * as Effect from "effect/Effect";
24+import type { LocalToolRuntime } from "../../sdk/src/runtime/local-tool-runtime";
25+import {
26+ type LoadedLocalExecutorConfig,
27+ type ResolvedLocalWorkspaceContext,
28+ loadLocalExecutorConfig,
29+ resolveConfigRelativePath,
30+ resolveLocalWorkspaceContext,
31+ writeProjectLocalExecutorConfig,
32+} from "./config";
33+import { createLocalControlPlanePersistence } from "./control-plane-store";
34+import {
35+ createDefaultSecretMaterialDeleter,
36+ createLocalInstanceConfigResolver,
37+ createDefaultSecretMaterialResolver,
38+ createDefaultSecretMaterialStorer,
39+ createDefaultSecretMaterialUpdater,
40+} from "./secret-material-providers";
41+import {
42+ buildLocalSourceArtifact,
43+ readLocalSourceArtifact,
44+ removeLocalSourceArtifact,
45+ writeLocalSourceArtifact,
46+} from "./source-artifacts";
47+import {
48+ getOrProvisionLocalInstallation,
49+ loadLocalInstallation,
50+} from "./installation";
51+import {
52+ loadLocalToolRuntime,
53+} from "./tools";
54+import {
55+ loadLocalWorkspaceState,
56+ writeLocalWorkspaceState,
57+} from "./workspace-state";
58+import {
59+ refreshSourceTypeDeclarationInBackground,
60+ refreshWorkspaceSourceTypeDeclarationsInBackground,
61+} from "./source-type-declarations";
62+
63+const toError = (cause: unknown): Error =>
64+ cause instanceof Error ? cause : new Error(String(cause));
65+
66+const 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+
72+export type CreateLocalExecutorBackendOptions = {
73+ cwd?: string;
74+ workspaceRoot?: string;
75+ homeConfigPath?: string;
76+ homeStateDirectory?: string;
77+ localDataDir?: string;
78+};
79+
80+type CreateLocalRuntimeOptions = CreateLocalExecutorBackendOptions &
81+ RuntimeControlPlaneOptions;
82+
83+type LocalWorkspaceState = Parameters<
84+ typeof writeLocalWorkspaceState
85+>[0]["state"];
86+
87+type LocalSourceArtifact = Parameters<
88+ typeof writeLocalSourceArtifact
89+>[0]["artifact"];
90+
91+const 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+
104+const 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+
124+const 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+
142+const 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+
172+const createBoundLocalToolRuntimeLoader = (
173+ context: ResolvedLocalWorkspaceContext,
174+ fileSystem: FileSystem.FileSystem,
175+): ExecutorBackendServices["localToolRuntimeLoader"] => ({
176+ load: () =>
177+ bindFileSystem(fileSystem, loadLocalToolRuntime(context)),
178+});
179+
180+const 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+
200+const 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+
216+export 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+
286+export 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+
300+export 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+
315+export 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+
326+export const createLocalExecutor = (
327+ options: CreateLocalExecutorBackendOptions & ExecutorRuntimeOptions = {},
328+): Promise<Executor> => Effect.runPromise(createLocalExecutorEffect(options));
329+
330+export {
331+ deriveLocalInstallation,
332+ getOrProvisionLocalInstallation,
333+ loadLocalInstallation,
334+} from "./installation";
335+export {
336+ loadLocalControlPlaneState,
337+ writeLocalControlPlaneState,
338+} from "./control-plane-store";
339+export {
340+ loadLocalExecutorConfig,
341+ resolveConfigRelativePath,
342+ resolveLocalWorkspaceContext,
343+ writeProjectLocalExecutorConfig,
344+} from "./config";
345+export {
346+ loadLocalWorkspaceState,
347+ writeLocalWorkspaceState,
348+} from "./workspace-state";
349+export {
350+ buildLocalSourceArtifact,
351+ readLocalSourceArtifact,
352+ removeLocalSourceArtifact,
353+ writeLocalSourceArtifact,
354+} from "./source-artifacts";
355+export {
356+ refreshSourceTypeDeclarationInBackground,
357+ refreshWorkspaceSourceTypeDeclarationsInBackground,
358+ syncSourceTypeDeclarationNode,
359+ syncWorkspaceSourceTypeDeclarationsNode,
360+} from "./source-type-declarations";
361+export 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
55 import { describe, expect, it } from "@effect/vitest";
66 import * as Effect from "effect/Effect";
77
8−import { createLocalControlPlaneRuntime as createControlPlaneRuntime } from "../../../../sdk-file/src/index";
8+import { createLocalControlPlaneRuntime as createControlPlaneRuntime } from "./index";
99 import { getOrProvisionLocalInstallation } from "./installation";
1010 import { resolveLocalWorkspaceContext } from "./config";
1111
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
44 AccountIdSchema,
55 WorkspaceIdSchema,
66 type LocalInstallation,
7−} from "#schema";
7+} from "@executor/platform-sdk/schema";
88 import * as Effect from "effect/Effect";
99
1010 import 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
2828 };
2929
3030 export const releaseWorkspaceFixturesRoot = fileURLToPath(
31− new URL("../__fixtures__", import.meta.url),
31+ new URL("../../sdk/src/runtime/__fixtures__", import.meta.url),
3232 );
3333
3434 const 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
77 import {
88 type LocalConfigSecretProvider,
99 type LocalExecutorConfig,
10− type SecretMaterial,
1110 type SecretMaterialPurpose,
12− SecretMaterialIdSchema,
1311 type SecretRef,
14−} from "#schema";
15−import * as Context from "effect/Context";
12+ type SecretMaterial,
13+ SecretMaterialIdSchema,
14+} from "@executor/platform-sdk/schema";
1615 import * as Effect from "effect/Effect";
1716 import * as Layer from "effect/Layer";
1817 import * as Option from "effect/Option";
1918
19+import {
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";
32+import { fromConfigSecretProviderId } from "../../sdk/src/runtime/workspace/config-secrets";
33+import { getRuntimeLocalWorkspaceOption } from "../../sdk/src/runtime/workspace/runtime-context";
34+import { ControlPlaneStore } from "../../sdk/src/runtime/store";
35+import type { ControlPlaneStoreShape } from "../../sdk/src/runtime/store";
36+import { runtimeEffectError } from "../../sdk/src/runtime/effect-errors";
2037 import { resolveConfigRelativePath } from "./config";
21−import { fromConfigSecretProviderId } from "./config-secrets";
22−import { getRuntimeLocalWorkspaceOption } from "./runtime-context";
23−import { ControlPlaneStore } from "../store";
24−import type { ControlPlaneStoreShape } from "../store";
25−import { runtimeEffectError } from "../effect-errors";
2638
2739 export const ENV_SECRET_PROVIDER_ID = "env";
2840 export const PARAMS_SECRET_PROVIDER_ID = "params";
@@ -36,54 +48,6 @@ export type SecretStoreProviderId = · 7 unmodified lines
3648 | typeof KEYCHAIN_SECRET_PROVIDER_ID
3749 | typeof LOCAL_SECRET_PROVIDER_ID;
3850
39−export type SecretMaterialResolveContext = {
40− params?: Readonly<Record<string, string | undefined>>;
41−};
42−
43−export type ResolveSecretMaterial = (input: {
44− ref: SecretRef;
45− context?: SecretMaterialResolveContext;
46−}) => Effect.Effect<string, Error, never>;
47−
48−export type StoreSecretMaterial = (input: {
49− purpose: SecretMaterialPurpose;
50− value: string;
51− name?: string | null;
52−}) => Effect.Effect<SecretRef, Error, never>;
53−
54−export type DeleteSecretMaterial = (
55− ref: SecretRef,
56−) => Effect.Effect<boolean, Error, never>;
57−
58−export 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−
71−export class SecretMaterialResolverService extends Context.Tag(
72− "#runtime/SecretMaterialResolverService",
73−)<SecretMaterialResolverService, ResolveSecretMaterial>() {}
74−
75−export class SecretMaterialStorerService extends Context.Tag(
76− "#runtime/SecretMaterialStorerService",
77−)<SecretMaterialStorerService, StoreSecretMaterial>() {}
78−
79−export class SecretMaterialDeleterService extends Context.Tag(
80− "#runtime/SecretMaterialDeleterService",
81−)<SecretMaterialDeleterService, DeleteSecretMaterial>() {}
82−
83−export class SecretMaterialUpdaterService extends Context.Tag(
84− "#runtime/SecretMaterialUpdaterService",
85−)<SecretMaterialUpdaterService, UpdateSecretMaterial>() {}
86−
8751 type SecretMaterialProviderRuntime = {
8852 rows: ControlPlaneStoreShape;
8953 env: NodeJS.ProcessEnv;
@@ -1085,17 +1049,16 @@ export const createDefaultSecretMaterialResolver = (input: { · 995 unmodified lines
10851049
10861050 export const createDefaultSecretMaterialStorer = (input: {
10871051 rows: ControlPlaneStoreShape;
1088− storeProviderId?: SecretStoreProviderId;
10891052 dangerouslyAllowEnvSecrets?: boolean;
10901053 keychainServiceName?: string;
10911054 }): StoreSecretMaterial => {
10921055 const providers = createSecretMaterialProviderRegistry();
10931056 const runtime = createSecretMaterialProviderRuntime(input);
10941057
1095− return ({ purpose, value, name }) =>
1058+ return ({ purpose, value, name, providerId }) =>
10961059 Effect.gen(function* () {
10971060 const defaultStoreProviderId = yield* resolveDefaultSecretStoreProviderId({
1098− storeProviderId: input.storeProviderId,
1061+ storeProviderId: parseSecretStoreProviderId(providerId) ?? undefined,
10991062 env: runtime.env,
11001063 });
11011064 const provider = yield* getSecretMaterialProvider({
@@ -1182,7 +1145,7 @@ const resolveRuntimeSecretMaterialConfig = (input: { · 80 unmodified lines
11821145 ?? runtimeLocalWorkspace?.loadedConfig.config
11831146 ?? null,
11841147 workspaceRoot: input.workspaceRoot
1185− ?? runtimeLocalWorkspace?.context.workspaceRoot
1148+ ?? runtimeLocalWorkspace?.workspace.workspaceRoot
11861149 ?? null,
11871150 };
11881151 });
@@ -1213,7 +1176,6 @@ export const SecretMaterialResolverLive = (input: { · 24 unmodified lines
12131176 );
12141177
12151178 export const SecretMaterialStorerLive = (input: {
1216− storeProviderId?: SecretStoreProviderId;
12171179 dangerouslyAllowEnvSecrets?: boolean;
12181180 keychainServiceName?: string;
12191181 } = {}) =>
@@ -1224,7 +1186,6 @@ export const SecretMaterialStorerLive = (input: { · 4 unmodified lines
12241186
12251187 return createDefaultSecretMaterialStorer({
12261188 rows,
1227− storeProviderId: input.storeProviderId,
12281189 dangerouslyAllowEnvSecrets: input.dangerouslyAllowEnvSecrets,
12291190 keychainServiceName: input.keychainServiceName,
12301191 });
@@ -1267,7 +1228,6 @@ export const SecretMaterialUpdaterLive = (input: { · 36 unmodified lines
12671228
12681229 export const SecretMaterialLive = (input: {
12691230 resolveSecretMaterial?: ResolveSecretMaterial;
1270− storeProviderId?: SecretStoreProviderId;
12711231 dangerouslyAllowEnvSecrets?: boolean;
12721232 keychainServiceName?: string;
12731233 localConfig?: LocalExecutorConfig | null;
@@ -1279,3 +1239,46 @@ export const SecretMaterialLive = (input: { · 5 unmodified lines
12791239 SecretMaterialDeleterLive(input),
12801240 SecretMaterialUpdaterLive(input),
12811241 );
1242+
1243+export 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+
1283+export 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
55 import { describe, expect, it } from "@effect/vitest";
66 import * as Effect from "effect/Effect";
77
8−import { SourceIdSchema, WorkspaceIdSchema, type Source } from "#schema";
8+import {
9+ SourceIdSchema,
10+ WorkspaceIdSchema,
11+ type Source,
12+} from "@executor/platform-sdk/schema";
913
1014 import {
1115 buildLocalSourceArtifact,
@@ -76,7 +80,7 @@ const makeArtifact = () => { · 64 unmodified lines
7680 documents: [
7781 {
7882 documentKind: "openapi",
79− documentKey: source.binding.specUrl,
83+ documentKey: String(source.binding.specUrl),
8084 contentText: '{"openapi":"3.1.0"}',
8185 fetchedAt: 1,
8286 },
packages/platform/sdk-file/src/source-artifacts.ts+172−0
@@ -0,0 +1,172 @@
1+import { join } from "node:path";
2+import { FileSystem } from "@effect/platform";
3+
4+import type { NativeBlob } from "@executor/ir/model";
5+import * as Effect from "effect/Effect";
6+import type { Source } from "@executor/platform-sdk/schema";
7+import type { SourceCatalogSyncResult } from "@executor/source-core";
8+import {
9+ buildLocalSourceArtifact,
10+ decodeStoredLocalSourceArtifact,
11+ hydrateLocalSourceArtifactDocuments,
12+ splitLocalSourceArtifactDocuments,
13+ type LocalSourceArtifact,
14+} from "../../sdk/src/runtime/source-artifacts";
15+import type { ResolvedLocalWorkspaceContext } from "./config";
16+import {
17+ LocalFileSystemError,
18+ unknownLocalErrorDetails,
19+} from "./errors";
20+
21+const 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+
29+const localSourceArtifactPath = (input: {
30+ context: ResolvedLocalWorkspaceContext;
31+ sourceId: string;
32+}): string =>
33+ join(input.context.artifactsDirectory, "sources", `${input.sourceId}.json`);
34+
35+const localSourceDocumentDirectory = (input: {
36+ context: ResolvedLocalWorkspaceContext;
37+ sourceId: string;
38+}): string =>
39+ join(input.context.artifactsDirectory, "sources", input.sourceId, "documents");
40+
41+const localSourceDocumentPath = (input: {
42+ context: ResolvedLocalWorkspaceContext;
43+ sourceId: string;
44+ documentId: string;
45+}): string =>
46+ join(localSourceDocumentDirectory(input), `${input.documentId}.txt`);
47+
48+export { buildLocalSourceArtifact };
49+export type { LocalSourceArtifact };
50+
51+export 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+
109+export 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+
151+export 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
55 import { describe, expect, it } from "@effect/vitest";
66 import * as Effect from "effect/Effect";
77
8−import { SourceIdSchema, WorkspaceIdSchema, type Source } from "#schema";
8+import {
9+ SourceIdSchema,
10+ WorkspaceIdSchema,
11+ type Source,
12+} from "@executor/platform-sdk/schema";
913
1014 import {
1115 createCatalogSnapshotV1,
@@ -21,7 +25,7 @@ import { · 9 unmodified lines
2125 ShapeSymbolIdSchema,
2226 } from "@executor/ir/ids";
2327 import type { CatalogV1, ProvenanceRef } from "@executor/ir/model";
24−import { syncWorkspaceSourceTypeDeclarationsNode } from "./type-declarations";
28+import { syncWorkspaceSourceTypeDeclarationsNode } from "./source-type-declarations";
2529
2630 const put = <K extends string, V>(record: Record<K, V>, key: K, value: V) => {
2731 record[key] = value;
packages/platform/sdk-file/src/source-type-declarations.ts+427−0
@@ -0,0 +1,427 @@
1+import { join } from "node:path";
2+import { FileSystem } from "@effect/platform";
3+import { NodeFileSystem } from "@effect/platform-node";
4+import type { Source } from "@executor/platform-sdk/schema";
5+import * as Cause from "effect/Cause";
6+import * as Effect from "effect/Effect";
7+import * as Fiber from "effect/Fiber";
8+import type { CatalogSnapshotV1 } from "@executor/ir/model";
9+
10+import { projectCatalogForAgentSdk } from "@executor/ir/catalog";
11+import {
12+ createCatalogTypeProjector,
13+ formatPropertyKey,
14+ joinTypeNameSegments,
15+ projectedCatalogTypeRoots,
16+ shapeAllowsOmittedArgs,
17+} from "../../sdk/src/runtime/catalog/catalog-typescript";
18+import type { ResolvedLocalWorkspaceContext } from "./config";
19+import {
20+ LocalFileSystemError,
21+ unknownLocalErrorDetails,
22+} from "./errors";
23+
24+type SourceDeclarationEntry = {
25+ source: Source;
26+ snapshot: CatalogSnapshotV1;
27+};
28+
29+type ToolMethodNode = {
30+ readonly segments: readonly string[];
31+ readonly inputType: string;
32+ readonly outputType: string;
33+ readonly argsOptional: boolean;
34+};
35+
36+type SourceDeclarationModel = {
37+ readonly methods: readonly ToolMethodNode[];
38+ readonly supportingTypes: readonly string[];
39+};
40+
41+type ToolTreeNode = {
42+ method: ToolMethodNode | null;
43+ children: Map<string, ToolTreeNode>;
44+};
45+
46+type SourceDeclarationStub = {
47+ sourceId: string;
48+};
49+
50+const GENERATED_TYPES_DIRECTORY = "types";
51+const GENERATED_SOURCE_TYPES_DIRECTORY = "sources";
52+
53+const 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+
61+const declarationDirectory = (context: ResolvedLocalWorkspaceContext): string =>
62+ join(context.configDirectory, GENERATED_TYPES_DIRECTORY);
63+
64+const sourceDeclarationDirectory = (context: ResolvedLocalWorkspaceContext): string =>
65+ join(declarationDirectory(context), GENERATED_SOURCE_TYPES_DIRECTORY);
66+
67+const sourceDeclarationFileName = (sourceId: string): string => `${sourceId}.d.ts`;
68+
69+const sourceDeclarationPath = (context: ResolvedLocalWorkspaceContext, sourceId: string): string =>
70+ join(sourceDeclarationDirectory(context), sourceDeclarationFileName(sourceId));
71+
72+const aggregateDeclarationPath = (context: ResolvedLocalWorkspaceContext): string =>
73+ join(declarationDirectory(context), "index.d.ts");
74+
75+const sourceTypeInterfaceName = (sourceId: string): string =>
76+ `SourceTools_${sourceId.replace(/[^A-Za-z0-9_$]+/g, "_")}`;
77+
78+const methodSignature = (method: ToolMethodNode): string =>
79+ `(${method.argsOptional ? "args?:" : "args:"} ${method.inputType}) => Promise<${method.outputType}>`;
80+
81+const createToolTreeNode = (): ToolTreeNode => ({
82+ method: null,
83+ children: new Map(),
84+});
85+
86+const 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+
101+const 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+
120+const 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+
143+const 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+
174+const 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+
195+const aggregateDeclarationText = (entries: readonly SourceDeclarationEntry[]): string => {
196+ return aggregateDeclarationTextFromSourceIds(
197+ entries.map((entry) => ({ sourceId: entry.source.id })),
198+ );
199+};
200+
201+const 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+
227+export 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+
280+export 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+
342+export 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+
348+export 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+
355+const 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+
364+const BACKGROUND_DECLARATION_REFRESH_DELAY = "1500 millis";
365+const workspaceRefreshFibers = new Map<string, Fiber.RuntimeFiber<void, never>>();
366+const sourceRefreshFibers = new Map<string, Fiber.RuntimeFiber<void, never>>();
367+
368+export 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+
398+export 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
1515 type ToolMetadata,
1616 type ToolPath,
1717 } from "@executor/codemode-core";
18−import * as Context from "effect/Context";
1918 import * as Effect from "effect/Effect";
20−import * as Layer from "effect/Layer";
2119 import * as ts from "typescript";
2220
21+import type { LocalToolRuntime } from "../../sdk/src/runtime/local-tool-runtime";
2322 import type { ResolvedLocalWorkspaceContext } from "./config";
2423 import {
2524 LocalFileSystemError,
@@ -79,13 +78,6 @@ export const defineLocalTool = < · 53 unmodified lines
7978 | LocalToolWithMetadata<TInputSchema, TOutputSchema>,
8079 ): typeof input => input;
8180
82−export type LocalToolRuntime = {
83− tools: ToolMap;
84− catalog: ToolCatalog;
85− toolInvoker: ToolInvoker;
86− toolPaths: Set<string>;
87−};
88−
8981 const emptyLocalToolRuntime = (): LocalToolRuntime => {
9082 const tools: ToolMap = {};
9183 return {
@@ -651,35 +643,3 @@ export const loadLocalToolRuntime = ( · 559 unmodified lines
651643 toolPaths: new Set(Object.keys(tools)),
652644 } satisfies LocalToolRuntime;
653645 });
654−
655−export 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−
669−export class LocalToolRuntimeLoaderService extends Context.Tag(
670− "#runtime/LocalToolRuntimeLoaderService",
671−)<LocalToolRuntimeLoaderService, LocalToolRuntimeLoaderShape>() {}
672−
673−export 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 @@
11 import { join } from "node:path";
22 import { FileSystem } from "@effect/platform";
3−
4−import {
5− PolicyIdSchema,
6− SourceStatusSchema,
7− TimestampMsSchema,
8−} from "#schema";
93 import * as Effect from "effect/Effect";
104 import * as Schema from "effect/Schema";
115
6+import type { LocalWorkspaceState } from "../../sdk/src/runtime/workspace-state";
7+import {
8+ defaultLocalWorkspaceState,
9+ LocalWorkspaceStateSchema,
10+} from "../../sdk/src/runtime/workspace-state";
1211 import type { ResolvedLocalWorkspaceContext } from "./config";
1312 import {
1413 LocalFileSystemError,
@@ -18,36 +17,6 @@ import { · 3 unmodified lines
1817
1918 const WORKSPACE_STATE_BASENAME = "workspace-state.json";
2019
21−const 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−
29−const LocalWorkspacePolicyStateSchema = Schema.Struct({
30− id: PolicyIdSchema,
31− createdAt: TimestampMsSchema,
32− updatedAt: TimestampMsSchema,
33−});
34−
35−export 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−
47−export type LocalWorkspaceSourceState = typeof LocalWorkspaceSourceStateSchema.Type;
48−export type LocalWorkspacePolicyState = typeof LocalWorkspacePolicyStateSchema.Type;
49−export type LocalWorkspaceState = typeof LocalWorkspaceStateSchema.Type;
50−
5120 const decodeLocalWorkspaceState = Schema.decodeUnknownSync(LocalWorkspaceStateSchema);
5221
5322 const mapFileSystemError = (path: string, action: string) => (cause: unknown) =>
@@ -58,12 +27,6 @@ const mapFileSystemError = (path: string, action: string) => (cause: unknown) => · 4 unmodified lines
5827 details: unknownLocalErrorDetails(cause),
5928 });
6029
61−const defaultLocalWorkspaceState = (): LocalWorkspaceState => ({
62− version: 1,
63− sources: {},
64− policies: {},
65−});
66−
6730 export const localWorkspaceStatePath = (
6831 context: ResolvedLocalWorkspaceContext,
6932 ): string => join(context.stateDirectory, WORKSPACE_STATE_BASENAME);
@@ -90,13 +53,12 @@ export const loadLocalWorkspaceState = ( · 20 unmodified lines
9053 );
9154 return yield* Effect.try({
9255 try: () => decodeLocalWorkspaceState(JSON.parse(content) as unknown),
93− catch: (cause) => {
94− return new LocalWorkspaceStateDecodeError({
56+ catch: (cause) =>
57+ new LocalWorkspaceStateDecodeError({
9558 message: `Invalid local workspace state at ${path}: ${unknownLocalErrorDetails(cause)}`,
9659 path,
9760 details: unknownLocalErrorDetails(cause),
98− });
99− },
61+ }),
10062 });
10163 });
10264
@@ -107,12 +69,12 @@ export const writeLocalWorkspaceState = (input: { · 4 unmodified lines
10769 Effect.gen(function* () {
10870 const fs = yield* FileSystem.FileSystem;
10971 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+ ),
11175 );
112− yield* fs.writeFileString(
113− localWorkspaceStatePath(input.context),
114− `${JSON.stringify(input.state, null, 2)}\n`,
115− ).pipe(
116− Effect.mapError(mapFileSystemError(localWorkspaceStatePath(input.context), "write workspace state")),
76+ const path = localWorkspaceStatePath(input.context);
77+ yield* fs.writeFileString(path, `${JSON.stringify(input.state, null, 2)}\n`).pipe(
78+ Effect.mapError(mapFileSystemError(path, "write workspace state")),
11779 );
11880 });
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
1818 "#schema": "./src/schema/index.ts"
1919 },
2020 "scripts": {
21− "fixture:release:capture": "bun run ./src/runtime/local/capture-release-workspace-fixture.ts",
2221 "test": "vitest run",
2322 "test:watch": "vitest",
2423 "typecheck": "bunx tsc --noEmit -p tsconfig.json"
packages/platform/sdk/src/backend.ts+4−0
@@ -12,6 +12,10 @@ import { · 11 unmodified lines
1212 type RuntimeControlPlaneServices,
1313 type RuntimeSecretMaterialServices,
1414 } from "./runtime";
15+export type {
16+ ExecutorWorkspaceContext,
17+ ExecutorWorkspaceDescriptor,
18+} from "./workspace";
1519
1620 export type ExecutorBackend = {
1721 createRuntime: (
packages/platform/sdk/src/local/operations.ts+9−23
@@ -14,12 +14,7 @@ import * as Option from "effect/Option"; · 13 unmodified lines
1414 import { submitExecutionInteractionResponse } from "../runtime/execution/service";
1515 import { LiveExecutionManagerService } from "../runtime/execution/live";
1616 import { operationErrors } from "../runtime/policy/operation-errors";
17−import { resolveLocalWorkspaceContext } from "../runtime/local/config";
18−import {
19− InstallationStore,
20− LocalInstallationStore,
21−} from "../runtime/local/storage";
22−import { getRuntimeLocalWorkspaceOption } from "../runtime/local/runtime-context";
17+import { InstallationStore } from "../runtime/workspace/storage";
2318 import { RuntimeSourceAuthServiceTag } from "../runtime/sources/source-auth-service";
2419 import {
2520 createSourceCredentialSelectionBearerContent,
@@ -190,24 +185,15 @@ const loadSourceCredentialInteraction = (input: { · 164 unmodified lines
190185
191186 export const getLocalInstallation = () =>
192187 Effect.gen(function* () {
193− const runtimeLocalWorkspace = yield* getRuntimeLocalWorkspaceOption();
194− const context =
195− runtimeLocalWorkspace?.context ??
196− (yield* resolveLocalWorkspaceContext().pipe(
197− Effect.mapError((error) =>
198− localOps.installation.unknownStorage(
199− error,
200− "Failed resolving local workspace context",
201− ),
188+ const installationStore = yield* InstallationStore;
189+ return yield* installationStore.load().pipe(
190+ Effect.mapError((error) =>
191+ localOps.installation.unknownStorage(
192+ error,
193+ "Failed loading local installation",
202194 ),
203− ));
204−
205− const installationStore =
206− runtimeLocalWorkspace !== null
207− ? yield* InstallationStore
208− : LocalInstallationStore;
209−
210− return yield* installationStore.load(context);
195+ ),
196+ );
211197 });
212198
213199 export const getSourceCredentialInteraction = (input: {
packages/platform/sdk/src/local/secrets.ts+12−72
@@ -16,22 +16,16 @@ import { · 15 unmodified lines
1616 ControlPlaneNotFoundError,
1717 ControlPlaneStorageError,
1818 } from "../errors";
19−import { requireRuntimeLocalWorkspace } from "../runtime/local/runtime-context";
19+import { requireRuntimeLocalWorkspace } from "../runtime/workspace/runtime-context";
2020 import {
21− createDefaultSecretMaterialDeleter,
22− createDefaultSecretMaterialStorer,
23− createDefaultSecretMaterialUpdater,
24− ENV_SECRET_PROVIDER_ID,
25− KEYCHAIN_SECRET_PROVIDER_ID,
26− LOCAL_SECRET_PROVIDER_ID,
27− parseSecretStoreProviderId,
28− resolveDefaultSecretStoreProviderId,
29−} from "../runtime/local/secret-material-providers";
21+ LocalInstanceConfigService,
22+ SecretMaterialDeleterService,
23+ SecretMaterialStorerService,
24+ SecretMaterialUpdaterService,
25+} from "../runtime/workspace/secret-material-providers";
3026 import { RuntimeSourceStoreService } from "../runtime/sources/source-store";
3127 import { ControlPlaneStore } from "../runtime/store";
3228
33−const SECRET_STORE_PROVIDER_ENV = "EXECUTOR_SECRET_STORE_PROVIDER";
34−
3529 const secretStorageError = (operation: string, message: string) =>
3630 new ControlPlaneStorageError({
3731 operation,
@@ -39,45 +33,8 @@ const secretStorageError = (operation: string, message: string) => · 1 unmodified line
3933 details: message,
4034 });
4135
42−export const getLocalInstanceConfig = (): Effect.Effect<InstanceConfig> => {
43− const explicitDefaultStoreProvider = parseSecretStoreProviderId(
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−};
36+export const getLocalInstanceConfig = (): Effect.Effect<InstanceConfig, Error, LocalInstanceConfigService> =>
37+ Effect.flatMap(LocalInstanceConfigService, (resolveInstanceConfig) => resolveInstanceConfig());
8138
8239 export const listLocalSecrets = () =>
8340 Effect.gen(function* () {
@@ -119,10 +76,6 @@ export const createLocalSecret = (payload: CreateSecretPayload) => · 35 unmodified lines
11976 const name = payload.name.trim();
12077 const value = payload.value;
12178 const purpose = payload.purpose ?? "auth_material";
122− const requestedProviderId =
123− payload.providerId === undefined
124− ? null
125− : parseSecretStoreProviderId(payload.providerId);
12679
12780 if (name.length === 0) {
12881 return yield* new ControlPlaneBadRequestError({
@@ -131,23 +84,14 @@ export const createLocalSecret = (payload: CreateSecretPayload) => · 2 unmodified lines
13184 details: "Secret name is required.",
13285 });
13386 }
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− }
14187
14288 const store = yield* ControlPlaneStore;
143− const storeSecretMaterial = createDefaultSecretMaterialStorer({
144− rows: store,
145− ...(requestedProviderId ? { storeProviderId: requestedProviderId } : {}),
146− });
89+ const storeSecretMaterial = yield* SecretMaterialStorerService;
14790 const ref = yield* storeSecretMaterial({
14891 name,
14992 purpose,
15093 value,
94+ providerId: payload.providerId,
15195 }).pipe(
15296 Effect.mapError((cause) =>
15397 secretStorageError(
@@ -214,9 +158,7 @@ export const updateLocalSecret = (input: { · 60 unmodified lines
214158 update.name = input.payload.name.trim() || null;
215159 if (input.payload.value !== undefined) update.value = input.payload.value;
216160
217− const updateSecretMaterial = createDefaultSecretMaterialUpdater({
218− rows: store,
219− });
161+ const updateSecretMaterial = yield* SecretMaterialUpdaterService;
220162 const updated = yield* updateSecretMaterial({
221163 ref: {
222164 providerId: existing.value.providerId,
@@ -260,9 +202,7 @@ export const deleteLocalSecret = (secretId: string) => · 37 unmodified lines
260202 });
261203 }
262204
263− const deleteSecretMaterial = createDefaultSecretMaterialDeleter({
264− rows: store,
265− });
205+ const deleteSecretMaterial = yield* SecretMaterialDeleterService;
266206 const removed = yield* deleteSecretMaterial({
267207 providerId: existing.value.providerId,
268208 handle: existing.value.id,
packages/platform/sdk/src/policies/operations.ts+32−26
@@ -13,18 +13,17 @@ import { · 12 unmodified lines
1313 } from "../schema";
1414 import * as Effect from "effect/Effect";
1515
16−import { requireRuntimeLocalWorkspace } from "../runtime/local/runtime-context";
16+import { requireRuntimeLocalWorkspace } from "../runtime/workspace/runtime-context";
1717 import type {
1818 WorkspaceConfigStoreShape,
1919 WorkspaceStateStoreShape,
20−} from "../runtime/local/storage";
20+} from "../runtime/workspace/storage";
2121 import {
2222 WorkspaceConfigStore,
2323 WorkspaceStateStore,
24−} from "../runtime/local/storage";
25−import type { ResolvedLocalWorkspaceContext } from "../runtime/local/config";
26−import { type LocalWorkspaceState } from "../runtime/local/workspace-state";
27−import { derivePolicyConfigKey } from "../runtime/local/workspace-sync";
24+} from "../runtime/workspace/storage";
25+import { type LocalWorkspaceState } from "../runtime/workspace-state";
26+import { derivePolicyConfigKey } from "../runtime/workspace/workspace-sync";
2827 import {
2928 type OperationErrors,
3029 operationErrors,
@@ -40,17 +39,22 @@ const policyOps = { · 9 unmodified lines
4039
4140 const cloneJson = <T>(value: T): T => JSON.parse(JSON.stringify(value)) as T;
4241
42+const workspacePolicyStableKey = (input: {
43+ workspaceId: WorkspaceId;
44+ workspaceRoot?: string | null;
45+}): string => input.workspaceRoot?.trim() || input.workspaceId;
46+
4347 const localPolicyIdForKey = (input: {
44− workspaceRoot: string;
48+ workspaceStableKey: string;
4549 key: string;
4650 }): PolicyId =>
4751 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)}`,
4953 );
5054
5155 const toLocalWorkspacePolicy = (input: {
5256 workspaceId: WorkspaceId;
53− workspaceRoot: string;
57+ workspaceStableKey: string;
5458 key: string;
5559 policyConfig: NonNullable<LocalExecutorConfig["policies"]>[string];
5660 state: LocalWorkspaceState["policies"][string] | undefined;
@@ -58,7 +62,7 @@ const toLocalWorkspacePolicy = (input: { · 1 unmodified line
5862 id:
5963 input.state?.id ??
6064 localPolicyIdForKey({
61− workspaceRoot: input.workspaceRoot,
65+ workspaceStableKey: input.workspaceStableKey,
6266 key: input.key,
6367 }),
6468 key: input.key,
@@ -78,18 +82,17 @@ export const loadRuntimeLocalWorkspacePolicies = (workspaceId: WorkspaceId) => · 13 unmodified lines
7882 yield* requireRuntimeLocalWorkspace(workspaceId);
7983 const workspaceConfigStore = yield* WorkspaceConfigStore;
8084 const workspaceStateStore = yield* WorkspaceStateStore;
81− const loadedConfig = yield* workspaceConfigStore.load(
82− runtimeLocalWorkspace.context,
83− );
84− const workspaceState = yield* workspaceStateStore.load(
85− runtimeLocalWorkspace.context,
86− );
85+ const loadedConfig = yield* workspaceConfigStore.load();
86+ const workspaceState = yield* workspaceStateStore.load();
8787
8888 const policies = Object.entries(loadedConfig.config?.policies ?? {}).map(
8989 ([key, policyConfig]) =>
9090 toLocalWorkspacePolicy({
9191 workspaceId,
92− workspaceRoot: runtimeLocalWorkspace.context.workspaceRoot,
92+ workspaceStableKey: workspacePolicyStableKey({
93+ workspaceId,
94+ workspaceRoot: runtimeLocalWorkspace.workspace.workspaceRoot,
95+ }),
9396 key,
9497 policyConfig,
9598 state: workspaceState.policies[key],
@@ -106,7 +109,6 @@ export const loadRuntimeLocalWorkspacePolicies = (workspaceId: WorkspaceId) => · 10 unmodified lines
106109
107110 const writeLocalPolicyFiles = (input: {
108111 operation: OperationErrors;
109− context: ResolvedLocalWorkspaceContext;
110112 workspaceConfigStore: WorkspaceConfigStoreShape;
111113 workspaceStateStore: WorkspaceStateStoreShape;
112114 projectConfig: LocalExecutorConfig;
@@ -115,11 +117,9 @@ const writeLocalPolicyFiles = (input: { · 2 unmodified lines
115117 Effect.all(
116118 [
117119 input.workspaceConfigStore.writeProject({
118− context: input.context,
119120 config: input.projectConfig,
120121 }),
121122 input.workspaceStateStore.write({
122− context: input.context,
123123 state: input.workspaceState,
124124 }),
125125 ],
@@ -219,7 +219,10 @@ export const createPolicy = (input: { · 93 unmodified lines
219219 id:
220220 existingState?.id ??
221221 localPolicyIdForKey({
222− workspaceRoot: runtimeLocalWorkspace.context.workspaceRoot,
222+ workspaceStableKey: workspacePolicyStableKey({
223+ workspaceId: input.workspaceId,
224+ workspaceRoot: runtimeLocalWorkspace.workspace.workspaceRoot,
225+ }),
223226 key,
224227 }),
225228 createdAt: existingState?.createdAt ?? now,
@@ -230,7 +233,6 @@ export const createPolicy = (input: { · 4 unmodified lines
230233
231234 yield* writeLocalPolicyFiles({
232235 operation: policyOps.create,
233− context: runtimeLocalWorkspace.context,
234236 workspaceConfigStore,
235237 workspaceStateStore,
236238 projectConfig: {
@@ -242,7 +244,10 @@ export const createPolicy = (input: { · 5 unmodified lines
242244
243245 return toLocalWorkspacePolicy({
244246 workspaceId: input.workspaceId,
245− workspaceRoot: runtimeLocalWorkspace.context.workspaceRoot,
247+ workspaceStableKey: workspacePolicyStableKey({
248+ workspaceId: input.workspaceId,
249+ workspaceRoot: runtimeLocalWorkspace.workspace.workspaceRoot,
250+ }),
246251 key,
247252 policyConfig: policies[key]!,
248253 state: workspaceState.policies[key],
@@ -355,7 +360,6 @@ export const updatePolicy = (input: { · 106 unmodified lines
355360
356361 yield* writeLocalPolicyFiles({
357362 operation: policyOps.update,
358− context: runtimeLocalWorkspace.context,
359363 workspaceConfigStore,
360364 workspaceStateStore,
361365 projectConfig: {
@@ -367,7 +371,10 @@ export const updatePolicy = (input: { · 5 unmodified lines
367371
368372 return toLocalWorkspacePolicy({
369373 workspaceId: input.workspaceId,
370− workspaceRoot: runtimeLocalWorkspace.context.workspaceRoot,
374+ workspaceStableKey: workspacePolicyStableKey({
375+ workspaceId: input.workspaceId,
376+ workspaceRoot: runtimeLocalWorkspace.workspace.workspaceRoot,
377+ }),
371378 key: existing.key,
372379 policyConfig: policies[existing.key]!,
373380 state: workspaceState.policies[existing.key],
@@ -413,7 +420,6 @@ export const removePolicy = (input: { · 39 unmodified lines
413420 localWorkspace.workspaceState.policies;
414421 yield* writeLocalPolicyFiles({
415422 operation: policyOps.remove,
416− context: runtimeLocalWorkspace.context,
417423 workspaceConfigStore,
418424 workspaceStateStore,
419425 projectConfig: {
packages/platform/sdk/src/runtime/__fixtures__/README.md+2−2
@@ -9,7 +9,7 @@ Each fixture directory should contain: · 8 unmodified lines
99 - `.executor/state/workspace-state.json`: a trimmed workspace state with only the captured source
1010 - `.executor/artifacts/sources/...`: the real artifact JSON and any sidecar files that release wrote
1111
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.
1313
1414 ## Capture A New Release Fixture
1515
@@ -35,4 +35,4 @@ On releases where you want upgrade coverage: · 19 unmodified lines
3535 1. Start from a real workspace that has a fully synced source on the released version.
3636 2. Run `bun run fixture:release:capture -- ...` for that source.
3737 3. Review the fixture for secrets before committing it.
38−4. Run `bun run --cwd packages/platform/sdk test -- src/runtime/local/source-artifacts.test.ts`.
38+4. 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
3535 import type {
3636 ResolveSecretMaterial,
3737 SecretMaterialResolveContext,
38−} from "../local/secret-material-providers";
38+} from "../workspace/secret-material-providers";
3939 import { runtimeEffectError } from "../effect-errors";
4040
4141 export 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
1919 import type { ControlPlaneStoreShape } from "../store";
2020 import { resolveAuthArtifactMaterial, type ResolvedSourceAuthMaterial } from "./auth-artifacts";
2121 import {
22− createDefaultSecretMaterialDeleter,
23− createDefaultSecretMaterialStorer,
22+ type DeleteSecretMaterial,
2423 type ResolveSecretMaterial,
2524 type SecretMaterialResolveContext,
26−} from "../local/secret-material-providers";
25+ type StoreSecretMaterial,
26+} from "../workspace/secret-material-providers";
2727 import { refreshOAuth2AccessToken } from "./oauth2-pkce";
2828 import type { OAuth2TokenResponse } from "./oauth2-pkce";
2929 import { createPersistedMcpAuthProvider } from "./mcp-auth-provider";
@@ -43,13 +43,12 @@ const cleanupAuthLeaseSecretRefs = ( · 13 unmodified lines
4343 previous: Pick<AuthLease, "placementsTemplateJson"> | null;
4444 next: Pick<AuthLease, "placementsTemplateJson"> | null;
4545 },
46+ deleteSecretMaterial: DeleteSecretMaterial,
4647 ) =>
4748 Effect.gen(function* () {
4849 if (input.previous === null) {
4950 return;
5051 }
51−
52− const deleteSecretMaterial = createDefaultSecretMaterialDeleter({ rows });
5352 const nextRefKeys = new Set(
5453 (input.next === null ? [] : authLeaseSecretRefs(input.next)).map(secretRefKey),
5554 );
@@ -85,6 +84,8 @@ const refreshRefreshableOauth2AuthorizedUserArtifact = (input: { · 29 unmodified lines
8584 artifact: AuthArtifact;
8685 lease: AuthLease | null;
8786 resolveSecretMaterial: ResolveSecretMaterial;
87+ storeSecretMaterial: StoreSecretMaterial;
88+ deleteSecretMaterial: DeleteSecretMaterial;
8889 context?: SecretMaterialResolveContext;
8990 }): Effect.Effect<AuthLease, Error, never> =>
9091 Effect.gen(function* () {
@@ -116,10 +117,7 @@ const refreshRefreshableOauth2AuthorizedUserArtifact = (input: { · 25 unmodified lines
116117 refreshToken,
117118 });
118119
119− const storeSecretMaterial = createDefaultSecretMaterialStorer({
120− rows: input.rows,
121− });
122− const accessTokenRef = yield* storeSecretMaterial({
120+ const accessTokenRef = yield* input.storeSecretMaterial({
123121 purpose: "oauth_access_token",
124122 value: tokenResponse.access_token,
125123 name: `${decoded.config.clientId} Access Token`,
@@ -166,7 +164,7 @@ const refreshRefreshableOauth2AuthorizedUserArtifact = (input: { · 40 unmodified lines
166164 yield* cleanupAuthLeaseSecretRefs(input.rows, {
167165 previous: input.lease,
168166 next: nextLease,
169− });
167+ }, input.deleteSecretMaterial);
170168
171169 return nextLease;
172170 });
@@ -185,10 +183,10 @@ const workspaceOauthClientSecretRef = (input: { · 12 unmodified lines
185183 const cleanupProviderGrantRefreshToken = (rows: ControlPlaneStoreShape, input: {
186184 previous: SecretRef;
187185 next: SecretRef;
188−}) =>
186+}, deleteSecretMaterial: DeleteSecretMaterial) =>
189187 secretRefKey(input.previous) === secretRefKey(input.next)
190188 ? Effect.void
191− : createDefaultSecretMaterialDeleter({ rows })(input.previous).pipe(
189+ : deleteSecretMaterial(input.previous).pipe(
192190 Effect.either,
193191 Effect.ignore,
194192 );
@@ -198,6 +196,8 @@ const refreshProviderGrantRefArtifact = (input: { · 3 unmodified lines
198196 artifact: AuthArtifact;
199197 lease: AuthLease | null;
200198 resolveSecretMaterial: ResolveSecretMaterial;
199+ storeSecretMaterial: StoreSecretMaterial;
200+ deleteSecretMaterial: DeleteSecretMaterial;
201201 context?: SecretMaterialResolveContext;
202202 }): Effect.Effect<AuthLease, Error, never> =>
203203 Effect.gen(function* () {
@@ -239,17 +239,14 @@ const refreshProviderGrantRefArtifact = (input: { · 35 unmodified lines
239239 scopes: config.requiredScopes.length > 0 ? config.requiredScopes : null,
240240 });
241241
242− const storeSecretMaterial = createDefaultSecretMaterialStorer({
243− rows: input.rows,
244− });
245− const accessTokenRef = yield* storeSecretMaterial({
242+ const accessTokenRef = yield* input.storeSecretMaterial({
246243 purpose: "oauth_access_token",
247244 value: tokenResponse.access_token,
248245 name: `${grant.providerKey} Access Token`,
249246 });
250247
251248 const rotatedRefreshTokenRef = tokenResponse.refresh_token
252− ? yield* storeSecretMaterial({
249+ ? yield* input.storeSecretMaterial({
253250 purpose: "oauth_refresh_token",
254251 value: tokenResponse.refresh_token,
255252 name: `${grant.providerKey} Refresh Token`,
@@ -267,7 +264,7 @@ const refreshProviderGrantRefArtifact = (input: { · 11 unmodified lines
267264 yield* cleanupProviderGrantRefreshToken(input.rows, {
268265 previous: grant.refreshToken,
269266 next: rotatedRefreshTokenRef,
270− });
267+ }, input.deleteSecretMaterial);
271268
272269 const expiresInMs =
273270 typeof tokenResponse.expires_in === "number" && Number.isFinite(tokenResponse.expires_in)
@@ -310,14 +307,14 @@ const refreshProviderGrantRefArtifact = (input: { · 36 unmodified lines
310307 yield* cleanupAuthLeaseSecretRefs(input.rows, {
311308 previous: input.lease,
312309 next: nextLease,
313− });
310+ }, input.deleteSecretMaterial);
314311
315312 return nextLease;
316313 });
317314
318315 export const removeAuthLeaseAndSecrets = (rows: ControlPlaneStoreShape, input: {
319316 authArtifactId: AuthArtifact["id"];
320−}): Effect.Effect<void, Error, never> =>
317+}, deleteSecretMaterial: DeleteSecretMaterial): Effect.Effect<void, Error, never> =>
321318 Effect.gen(function* () {
322319 const existingLease = yield* rows.authLeases.getByAuthArtifactId(input.authArtifactId);
323320 if (Option.isNone(existingLease)) {
@@ -328,13 +325,15 @@ export const removeAuthLeaseAndSecrets = (rows: ControlPlaneStoreShape, input: { · 4 unmodified lines
328325 yield* cleanupAuthLeaseSecretRefs(rows, {
329326 previous: existingLease.value,
330327 next: null,
331− });
328+ }, deleteSecretMaterial);
332329 });
333330
334331 export const upsertOauth2AuthorizedUserLeaseFromTokenResponse = (input: {
335332 rows: ControlPlaneStoreShape;
336333 artifact: AuthArtifact;
337334 tokenResponse: OAuth2TokenResponse;
335+ storeSecretMaterial: StoreSecretMaterial;
336+ deleteSecretMaterial: DeleteSecretMaterial;
338337 }): Effect.Effect<void, Error, never> =>
339338 Effect.gen(function* () {
340339 const decoded = decodeBuiltInAuthArtifactConfig(input.artifact);
@@ -347,10 +346,7 @@ export const upsertOauth2AuthorizedUserLeaseFromTokenResponse = (input: { · 6 unmodified lines
347346
348347 const existingLeaseOption = yield* input.rows.authLeases.getByAuthArtifactId(input.artifact.id);
349348 const existingLease = Option.isSome(existingLeaseOption) ? existingLeaseOption.value : null;
350− const storeSecretMaterial = createDefaultSecretMaterialStorer({
351− rows: input.rows,
352− });
353− const accessTokenRef = yield* storeSecretMaterial({
349+ const accessTokenRef = yield* input.storeSecretMaterial({
354350 purpose: "oauth_access_token",
355351 value: input.tokenResponse.access_token,
356352 name: `${decoded.config.clientId} Access Token`,
@@ -397,13 +393,15 @@ export const upsertOauth2AuthorizedUserLeaseFromTokenResponse = (input: { · 40 unmodified lines
397393 yield* cleanupAuthLeaseSecretRefs(input.rows, {
398394 previous: existingLease,
399395 next: nextLease,
400− });
396+ }, input.deleteSecretMaterial);
401397 });
402398
403399 export const resolveAuthArtifactMaterialWithLeases = (input: {
404400 rows: ControlPlaneStoreShape;
405401 artifact: AuthArtifact | null;
406402 resolveSecretMaterial: ResolveSecretMaterial;
403+ storeSecretMaterial: StoreSecretMaterial;
404+ deleteSecretMaterial: DeleteSecretMaterial;
407405 context?: SecretMaterialResolveContext;
408406 }): Effect.Effect<ResolvedSourceAuthMaterial, Error, never> =>
409407 Effect.gen(function* () {
@@ -432,6 +430,8 @@ export const resolveAuthArtifactMaterialWithLeases = (input: { · 22 unmodified lines
432430 artifact: input.artifact,
433431 lease: existingLease,
434432 resolveSecretMaterial: input.resolveSecretMaterial,
433+ storeSecretMaterial: input.storeSecretMaterial,
434+ deleteSecretMaterial: input.deleteSecretMaterial,
435435 context: input.context,
436436 });
437437
@@ -451,6 +451,8 @@ export const resolveAuthArtifactMaterialWithLeases = (input: { · 13 unmodified lines
451451 artifact: input.artifact,
452452 lease: existingLease,
453453 resolveSecretMaterial: input.resolveSecretMaterial,
454+ storeSecretMaterial: input.storeSecretMaterial,
455+ deleteSecretMaterial: input.deleteSecretMaterial,
454456 context: input.context,
455457 });
456458
@@ -476,6 +478,8 @@ export const resolveAuthArtifactMaterialWithLeases = (input: { · 19 unmodified lines
476478 artifact: input.artifact,
477479 config: mcpOAuthConfig,
478480 resolveSecretMaterial: input.resolveSecretMaterial,
481+ storeSecretMaterial: input.storeSecretMaterial,
482+ deleteSecretMaterial: input.deleteSecretMaterial,
479483 context: input.context,
480484 }),
481485 };
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
2222
2323 import type { ControlPlaneStoreShape } from "../store";
2424 import {
25− createDefaultSecretMaterialDeleter,
26− createDefaultSecretMaterialStorer,
25+ type DeleteSecretMaterial,
2726 type ResolveSecretMaterial,
2827 type SecretMaterialResolveContext,
29−} from "../local/secret-material-providers";
28+ type StoreSecretMaterial,
29+} from "../workspace/secret-material-providers";
3030
3131 const encodeMcpOAuthAuthArtifactConfig = Schema.encodeSync(
3232 McpOAuthAuthArtifactConfigJsonSchema,
@@ -62,24 +62,23 @@ const secretRefEquals = (left: SecretRef | null, right: SecretRef | null): boole · 29 unmodified lines
6262 (left?.providerId ?? null) === (right?.providerId ?? null)
6363 && (left?.handle ?? null) === (right?.handle ?? null);
6464
65−const cleanupReplacedSecretRefs = (rows: ControlPlaneStoreShape, input: {
65+const cleanupReplacedSecretRefs = (input: {
66+ deleteSecretMaterial: DeleteSecretMaterial;
6667 previousAccessToken: SecretRef;
6768 nextAccessToken: SecretRef;
6869 previousRefreshToken: SecretRef | null;
6970 nextRefreshToken: SecretRef | null;
7071 }) =>
7172 Effect.gen(function* () {
72− const deleteSecretMaterial = createDefaultSecretMaterialDeleter({ rows });
73−
7473 if (!secretRefEquals(input.previousAccessToken, input.nextAccessToken)) {
75− yield* Effect.either(deleteSecretMaterial(input.previousAccessToken));
74+ yield* Effect.either(input.deleteSecretMaterial(input.previousAccessToken));
7675 }
7776
7877 if (
7978 input.previousRefreshToken !== null
8079 && !secretRefEquals(input.previousRefreshToken, input.nextRefreshToken)
8180 ) {
82− yield* Effect.either(deleteSecretMaterial(input.previousRefreshToken));
81+ yield* Effect.either(input.deleteSecretMaterial(input.previousRefreshToken));
8382 }
8483 });
8584
@@ -115,12 +114,10 @@ export const createPersistedMcpAuthProvider = (input: { · 29 unmodified lines
115114 artifact: AuthArtifact;
116115 config: McpOAuthAuthArtifactConfig;
117116 resolveSecretMaterial: ResolveSecretMaterial;
117+ storeSecretMaterial: StoreSecretMaterial;
118+ deleteSecretMaterial: DeleteSecretMaterial;
118119 context?: SecretMaterialResolveContext;
119120 }): OAuthClientProvider => {
120− const storeSecretMaterial = createDefaultSecretMaterialStorer({
121− rows: input.rows,
122− });
123−
124121 let currentArtifact = input.artifact;
125122 let currentConfig = input.config;
126123
@@ -186,13 +183,13 @@ export const createPersistedMcpAuthProvider = (input: { · 59 unmodified lines
186183 saveTokens: (tokens) =>
187184 Effect.runPromise(Effect.gen(function* () {
188185 const previousConfig = currentConfig;
189− const accessTokenRef = yield* storeSecretMaterial({
186+ const accessTokenRef = yield* input.storeSecretMaterial({
190187 purpose: "oauth_access_token",
191188 value: tokens.access_token,
192189 name: `${currentArtifact.sourceId} MCP Access Token`,
193190 });
194191 const refreshTokenRef = tokens.refresh_token
195− ? yield* storeSecretMaterial({
192+ ? yield* input.storeSecretMaterial({
196193 purpose: "oauth_refresh_token",
197194 value: tokens.refresh_token,
198195 name: `${currentArtifact.sourceId} MCP Refresh Token`,
@@ -212,7 +209,8 @@ export const createPersistedMcpAuthProvider = (input: { · 13 unmodified lines
212209 };
213210
214211 yield* persistConfig(nextConfig);
215− yield* cleanupReplacedSecretRefs(input.rows, {
212+ yield* cleanupReplacedSecretRefs({
213+ deleteSecretMaterial: input.deleteSecretMaterial,
216214 previousAccessToken: previousConfig.accessToken,
217215 nextAccessToken: accessTokenRef,
218216 previousRefreshToken: previousConfig.refreshToken,
packages/platform/sdk/src/runtime/auth/provider-grant-lifecycle.ts+4−5
@@ -9,7 +9,9 @@ import { · 8 unmodified lines
99 import * as Effect from "effect/Effect";
1010 import * as Option from "effect/Option";
1111
12−import { createDefaultSecretMaterialDeleter } from "../local/secret-material-providers";
12+import {
13+ type DeleteSecretMaterial,
14+} from "../workspace/secret-material-providers";
1315 import type { ControlPlaneStoreShape } from "../store";
1416
1517 const providerGrantRefFromArtifact = (
@@ -81,11 +83,8 @@ export const markProviderGrantOrphanedIfUnused = (rows: ControlPlaneStoreShape, · 65 unmodified lines
8183
8284 export const removeProviderAuthGrantSecret = (rows: ControlPlaneStoreShape, input: {
8385 grant: ProviderAuthGrant;
84−}): Effect.Effect<void, Error, never> =>
86+}, deleteSecretMaterial: DeleteSecretMaterial): Effect.Effect<void, Error, never> =>
8587 Effect.gen(function* () {
86− const deleteSecretMaterial = createDefaultSecretMaterialDeleter({
87− rows,
88− });
8988 yield* deleteSecretMaterial(input.grant.refreshToken).pipe(
9089 Effect.either,
9190 Effect.ignore,
packages/platform/sdk/src/runtime/auth/source-auth-material.ts+18−2
@@ -17,10 +17,16 @@ import { · 16 unmodified lines
1717 } from "./auth-artifacts";
1818 import { resolveAuthArtifactMaterialWithLeases } from "./auth-leases";
1919 import type {
20+ DeleteSecretMaterial,
2021 ResolveSecretMaterial,
2122 SecretMaterialResolveContext,
22−} from "../local/secret-material-providers";
23−import { SecretMaterialResolverService } from "../local/secret-material-providers";
23+ StoreSecretMaterial,
24+} from "../workspace/secret-material-providers";
25+import {
26+ SecretMaterialDeleterService,
27+ SecretMaterialResolverService,
28+ SecretMaterialStorerService,
29+} from "../workspace/secret-material-providers";
2430 import { runtimeEffectError } from "../effect-errors";
2531
2632 const authForSlot = (input: {
@@ -61,6 +67,8 @@ export const resolveSourceAuthMaterialWithDeps = (input: { · 34 unmodified lines
6167 actorAccountId?: AccountId | null;
6268 rows?: ControlPlaneStoreShape;
6369 resolveSecretMaterial: ResolveSecretMaterial;
70+ storeSecretMaterial?: StoreSecretMaterial;
71+ deleteSecretMaterial?: DeleteSecretMaterial;
6472 context?: SecretMaterialResolveContext;
6573 }): Effect.Effect<ResolvedSourceAuthMaterial, Error, never> =>
6674 Effect.gen(function* () {
@@ -85,6 +93,8 @@ export const resolveSourceAuthMaterialWithDeps = (input: { · 18 unmodified lines
8593 rows: input.rows,
8694 artifact: artifactOption.value,
8795 resolveSecretMaterial: input.resolveSecretMaterial,
96+ storeSecretMaterial: input.storeSecretMaterial ?? (() => Effect.dieMessage("storeSecretMaterial unavailable")),
97+ deleteSecretMaterial: input.deleteSecretMaterial ?? (() => Effect.dieMessage("deleteSecretMaterial unavailable")),
8898 context: input.context,
8999 });
90100 }
@@ -106,6 +116,8 @@ export const resolveSourceAuthMaterialWithDeps = (input: { · 15 unmodified lines
106116 rows: input.rows,
107117 artifact,
108118 resolveSecretMaterial: input.resolveSecretMaterial,
119+ storeSecretMaterial: input.storeSecretMaterial ?? (() => Effect.dieMessage("storeSecretMaterial unavailable")),
120+ deleteSecretMaterial: input.deleteSecretMaterial ?? (() => Effect.dieMessage("deleteSecretMaterial unavailable")),
109121 context: input.context,
110122 });
111123 }
@@ -138,6 +150,8 @@ export const RuntimeSourceAuthMaterialLive = Layer.effect( · 26 unmodified lines
138150 Effect.gen(function* () {
139151 const rows = yield* ControlPlaneStore;
140152 const resolveSecretMaterial = yield* SecretMaterialResolverService;
153+ const storeSecretMaterial = yield* SecretMaterialStorerService;
154+ const deleteSecretMaterial = yield* SecretMaterialDeleterService;
141155
142156 return RuntimeSourceAuthMaterialService.of({
143157 resolve: (input) =>
@@ -145,6 +159,8 @@ export const RuntimeSourceAuthMaterialLive = Layer.effect( · 1 unmodified line
145159 ...input,
146160 rows,
147161 resolveSecretMaterial,
162+ storeSecretMaterial,
163+ deleteSecretMaterial,
148164 }),
149165 });
150166 }),
packages/platform/sdk/src/runtime/catalog/source/reconcile.ts+2−3
@@ -5,8 +5,8 @@ import type { · 4 unmodified lines
55 } from "#schema";
66 import * as Effect from "effect/Effect";
77
8−import { RuntimeLocalWorkspaceService } from "../../local/runtime-context";
9−import { SourceArtifactStore } from "../../local/storage";
8+import { RuntimeLocalWorkspaceService } from "../../workspace/runtime-context";
9+import { SourceArtifactStore } from "../../workspace/storage";
1010 import { getSourceAdapterForSource } from "../../sources/source-adapters";
1111 import { RuntimeSourceStoreService } from "../../sources/source-store";
1212 import { RuntimeSourceCatalogSyncService } from "./sync";
@@ -42,7 +42,6 @@ export const reconcileMissingSourceCatalogArtifacts = (input: { · 29 unmodified lines
4242 }
4343
4444 const artifact = yield* sourceArtifactStore.read({
45− context: runtimeLocalWorkspace.context,
4645 sourceId: source.id,
4746 });
4847 if (artifact !== null) {
packages/platform/sdk/src/runtime/catalog/source/runtime.ts+11−20
@@ -27,7 +27,7 @@ import type { · 26 unmodified lines
2727 Executable,
2828 ShapeSymbol,
2929 } from "@executor/ir/model";
30−import { LocalSourceArtifactMissingError } from "../../local/errors";
30+import { LocalSourceArtifactMissingError } from "../../workspace-errors";
3131 import {
3232 createCatalogTypeProjector,
3333 documentationComment,
@@ -40,12 +40,12 @@ import { formatWithPrettier } from "../prettier-format"; · 6 unmodified lines
4040 import {
4141 RuntimeLocalWorkspaceService,
4242 type RuntimeLocalWorkspaceState,
43−} from "../../local/runtime-context";
44−import type { LocalSourceArtifact } from "../../local/source-artifacts";
43+} from "../../workspace/runtime-context";
44+import type { LocalSourceArtifact } from "../../source-artifacts";
4545 import {
4646 SourceArtifactStore,
4747 type SourceArtifactStoreShape,
48−} from "../../local/storage";
48+} from "../../workspace/storage";
4949 import {
5050 RuntimeSourceStoreService,
5151 type RuntimeSourceStore,
@@ -704,17 +704,16 @@ type SourceCatalogRuntimeServices = · 652 unmodified lines
704704 const ensureRuntimeCatalogWorkspace = (
705705 deps: RuntimeSourceCatalogStoreDeps,
706706 workspaceId: WorkspaceId,
707−) => {
707+) =>
708+ Effect.gen(function* () {
708709 if (deps.runtimeLocalWorkspace.installation.workspaceId !== workspaceId) {
709− return Effect.fail(
710− runtimeEffectError("catalog/source/runtime",
710+ return yield* Effect.fail(
711+ runtimeEffectError("catalog/source/runtime",
711712 `Runtime local workspace mismatch: expected ${workspaceId}, got ${deps.runtimeLocalWorkspace.installation.workspaceId}`,
712713 ),
713714 );
714715 }
715−
716− return Effect.succeed(deps.runtimeLocalWorkspace.context);
717−};
716+ });
718717
719718 const buildSnapshotFromArtifact = (input: {
720719 source: Source;
@@ -728,10 +727,7 @@ const loadWorkspaceSourceCatalogsWithDeps = (deps: RuntimeSourceCatalogStoreDeps · 7 unmodified lines
728727 actorAccountId?: AccountId | null;
729728 }): Effect.Effect<readonly LoadedSourceCatalog[], Error, never> =>
730729 Effect.gen(function* () {
731− const workspaceContext = yield* ensureRuntimeCatalogWorkspace(
732− deps,
733− input.workspaceId,
734− );
730+ yield* ensureRuntimeCatalogWorkspace(deps, input.workspaceId);
735731 const sources = yield* deps.sourceStore.loadSourcesInWorkspace(
736732 input.workspaceId,
737733 {
@@ -742,7 +738,6 @@ const loadWorkspaceSourceCatalogsWithDeps = (deps: RuntimeSourceCatalogStoreDeps · 4 unmodified lines
742738 const localCatalogs = yield* Effect.forEach(sources, (source) =>
743739 Effect.gen(function* () {
744740 const artifact = yield* deps.sourceArtifactStore.read({
745− context: workspaceContext,
746741 sourceId: source.id,
747742 });
748743 if (artifact === null) {
@@ -783,17 +778,13 @@ const loadSourceWithCatalogWithDeps = (deps: RuntimeSourceCatalogStoreDeps, inpu · 34 unmodified lines
783778 actorAccountId?: AccountId | null;
784779 }): Effect.Effect<LoadedSourceCatalog, Error | LocalSourceArtifactMissingError, never> =>
785780 Effect.gen(function* () {
786− const workspaceContext = yield* ensureRuntimeCatalogWorkspace(
787− deps,
788− input.workspaceId,
789− );
781+ yield* ensureRuntimeCatalogWorkspace(deps, input.workspaceId);
790782 const source = yield* deps.sourceStore.loadSourceById({
791783 workspaceId: input.workspaceId,
792784 sourceId: input.sourceId,
793785 actorAccountId: input.actorAccountId,
794786 });
795787 const artifact = yield* deps.sourceArtifactStore.read({
796− context: workspaceContext,
797788 sourceId: source.id,
798789 });
799790 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
1111 import {
1212 RuntimeLocalWorkspaceService,
1313 type RuntimeLocalWorkspaceState,
14−} from "../../local/runtime-context";
14+} from "../../workspace/runtime-context";
1515 import {
1616 SourceArtifactStore,
1717 type SourceArtifactStoreShape,
1818 WorkspaceStateStore,
1919 type WorkspaceStateStoreShape,
20−} from "../../local/storage";
20+} from "../../workspace/storage";
2121 import {
2222 type LocalWorkspaceState,
23−} from "../../local/workspace-state";
23+} from "../../workspace-state";
2424 import {
2525 RuntimeSourceAuthMaterialService,
2626 } from "../../auth/source-auth-material";
@@ -30,10 +30,11 @@ import { · 3 unmodified lines
3030 import {
3131 catalogSyncResultFromMcpManifest,
3232 } from "@executor/source-mcp";
33−import { SecretMaterialResolverService } from "../../local/secret-material-providers";
33+import { SecretMaterialResolverService } from "../../workspace/secret-material-providers";
3434 import { snapshotFromSourceCatalogSyncResult } from "@executor/source-core";
3535 import {
36− refreshSourceTypeDeclarationInBackground,
36+ SourceTypeDeclarationsRefresherService,
37+ type SourceTypeDeclarationsRefresherShape,
3738 } from "./type-declarations";
3839 import { runtimeEffectError } from "../../effect-errors";
3940
@@ -46,6 +47,7 @@ type RuntimeSourceCatalogSyncDeps = { · 6 unmodified lines
4647 runtimeLocalWorkspace: RuntimeLocalWorkspaceState;
4748 workspaceStateStore: WorkspaceStateStoreShape;
4849 sourceArtifactStore: SourceArtifactStoreShape;
50+ sourceTypeDeclarationsRefresher: SourceTypeDeclarationsRefresherShape;
4951 resolveSecretMaterial: Effect.Effect.Success<typeof SecretMaterialResolverService>;
5052 sourceAuthMaterialService: Effect.Effect.Success<typeof RuntimeSourceAuthMaterialService>;
5153 };
@@ -54,6 +56,7 @@ type SourceCatalogSyncServices = · 2 unmodified lines
5456 | RuntimeLocalWorkspaceService
5557 | WorkspaceStateStore
5658 | SourceArtifactStore
59+ | SourceTypeDeclarationsRefresherService
5760 | RuntimeSourceAuthMaterialService
5861 | SecretMaterialResolverService;
5962
@@ -75,17 +78,16 @@ export class RuntimeSourceCatalogSyncService extends Context.Tag( · 15 unmodified lines
7578 const ensureRuntimeCatalogSyncWorkspace = (
7679 deps: RuntimeSourceCatalogSyncDeps,
7780 workspaceId: Source["workspaceId"],
78−) => {
81+) =>
82+ Effect.gen(function* () {
7983 if (deps.runtimeLocalWorkspace.installation.workspaceId !== workspaceId) {
80− return Effect.fail(
84+ return yield* Effect.fail(
8185 runtimeEffectError("catalog/source/sync",
8286 `Runtime local workspace mismatch: expected ${workspaceId}, got ${deps.runtimeLocalWorkspace.installation.workspaceId}`,
8387 ),
8488 );
8589 }
86−
87− return Effect.succeed(deps.runtimeLocalWorkspace.context);
88−};
90+ });
8991
9092 const syncSourceCatalogWithDeps = (
9193 deps: RuntimeSourceCatalogSyncDeps,
@@ -95,13 +97,10 @@ const syncSourceCatalogWithDeps = ( · 3 unmodified lines
9597 },
9698 ): Effect.Effect<void, Error, never> =>
9799 Effect.gen(function* () {
98− const workspaceContext = yield* ensureRuntimeCatalogSyncWorkspace(
99− deps,
100− input.source.workspaceId,
101− );
100+ yield* ensureRuntimeCatalogSyncWorkspace(deps, input.source.workspaceId);
102101
103102 if (!shouldIndexSource(input.source)) {
104− const state = yield* deps.workspaceStateStore.load(workspaceContext);
103+ const state = yield* deps.workspaceStateStore.load();
105104 const existingSourceState = state.sources[input.source.id];
106105 const nextState: LocalWorkspaceState = {
107106 ...state,
@@ -117,15 +116,11 @@ const syncSourceCatalogWithDeps = ( · 9 unmodified lines
117116 },
118117 };
119118 yield* deps.workspaceStateStore.write({
120− context: workspaceContext,
121119 state: nextState,
122120 });
123− yield* Effect.sync(() => {
124− refreshSourceTypeDeclarationInBackground({
125− context: workspaceContext,
126− source: input.source,
127− snapshot: null,
128− });
121+ yield* deps.sourceTypeDeclarationsRefresher.refreshSourceInBackground({
122+ source: input.source,
123+ snapshot: null,
129124 });
130125 return;
131126 }
@@ -143,7 +138,6 @@ const syncSourceCatalogWithDeps = ( · 11 unmodified lines
143138 });
144139 const snapshot = snapshotFromSourceCatalogSyncResult(syncResult);
145140 yield* deps.sourceArtifactStore.write({
146− context: workspaceContext,
147141 sourceId: input.source.id,
148142 artifact: deps.sourceArtifactStore.build({
149143 source: input.source,
@@ -151,7 +145,7 @@ const syncSourceCatalogWithDeps = ( · 1 unmodified line
151145 }),
152146 });
153147
154− const state = yield* deps.workspaceStateStore.load(workspaceContext);
148+ const state = yield* deps.workspaceStateStore.load();
155149 const existingSourceState = state.sources[input.source.id];
156150 const nextState: LocalWorkspaceState = {
157151 ...state,
@@ -167,16 +161,12 @@ const syncSourceCatalogWithDeps = ( · 9 unmodified lines
167161 },
168162 };
169163 yield* deps.workspaceStateStore.write({
170− context: workspaceContext,
171164 state: nextState,
172165 });
173166
174− yield* Effect.sync(() => {
175− refreshSourceTypeDeclarationInBackground({
176− context: workspaceContext,
177− source: input.source,
178− snapshot,
179− });
167+ yield* deps.sourceTypeDeclarationsRefresher.refreshSourceInBackground({
168+ source: input.source,
169+ snapshot,
180170 });
181171 }).pipe(
182172 Effect.withSpan("source.catalog.sync", {
@@ -197,10 +187,7 @@ const persistMcpCatalogSnapshotFromManifestWithDeps = ( · 14 unmodified lines
197187 },
198188 ): Effect.Effect<void, Error, never> =>
199189 Effect.gen(function* () {
200− const workspaceContext = yield* ensureRuntimeCatalogSyncWorkspace(
201− deps,
202− input.source.workspaceId,
203− );
190+ yield* ensureRuntimeCatalogSyncWorkspace(deps, input.source.workspaceId);
204191 const syncResult = catalogSyncResultFromMcpManifest({
205192 source: input.source,
206193 endpoint: input.source.endpoint,
@@ -209,7 +196,6 @@ const persistMcpCatalogSnapshotFromManifestWithDeps = ( · 2 unmodified lines
209196 const snapshot = snapshotFromSourceCatalogSyncResult(syncResult);
210197
211198 yield* deps.sourceArtifactStore.write({
212− context: workspaceContext,
213199 sourceId: input.source.id,
214200 artifact: deps.sourceArtifactStore.build({
215201 source: input.source,
@@ -217,12 +203,9 @@ const persistMcpCatalogSnapshotFromManifestWithDeps = ( · 1 unmodified line
217203 }),
218204 });
219205
220− yield* Effect.sync(() => {
221− refreshSourceTypeDeclarationInBackground({
222− context: workspaceContext,
223− source: input.source,
224− snapshot,
225− });
206+ yield* deps.sourceTypeDeclarationsRefresher.refreshSourceInBackground({
207+ source: input.source,
208+ snapshot,
226209 });
227210 });
228211
@@ -234,6 +217,8 @@ export const syncSourceCatalog = (input: { · 5 unmodified lines
234217 const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService;
235218 const workspaceStateStore = yield* WorkspaceStateStore;
236219 const sourceArtifactStore = yield* SourceArtifactStore;
220+ const sourceTypeDeclarationsRefresher =
221+ yield* SourceTypeDeclarationsRefresherService;
237222 const resolveSecretMaterial = yield* SecretMaterialResolverService;
238223 const sourceAuthMaterialService = yield* RuntimeSourceAuthMaterialService;
239224
@@ -242,6 +227,7 @@ export const syncSourceCatalog = (input: { · 2 unmodified lines
242227 runtimeLocalWorkspace,
243228 workspaceStateStore,
244229 sourceArtifactStore,
230+ sourceTypeDeclarationsRefresher,
245231 resolveSecretMaterial,
246232 sourceAuthMaterialService,
247233 },
@@ -260,6 +246,8 @@ export const persistMcpCatalogSnapshotFromManifest = (input: { · 12 unmodified lines
260246 const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService;
261247 const workspaceStateStore = yield* WorkspaceStateStore;
262248 const sourceArtifactStore = yield* SourceArtifactStore;
249+ const sourceTypeDeclarationsRefresher =
250+ yield* SourceTypeDeclarationsRefresherService;
263251 const resolveSecretMaterial = yield* SecretMaterialResolverService;
264252 const sourceAuthMaterialService = yield* RuntimeSourceAuthMaterialService;
265253
@@ -268,6 +256,7 @@ export const persistMcpCatalogSnapshotFromManifest = (input: { · 2 unmodified lines
268256 runtimeLocalWorkspace,
269257 workspaceStateStore,
270258 sourceArtifactStore,
259+ sourceTypeDeclarationsRefresher,
271260 resolveSecretMaterial,
272261 sourceAuthMaterialService,
273262 },
@@ -281,6 +270,8 @@ export const RuntimeSourceCatalogSyncLive = Layer.effect( · 7 unmodified lines
281270 const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService;
282271 const workspaceStateStore = yield* WorkspaceStateStore;
283272 const sourceArtifactStore = yield* SourceArtifactStore;
273+ const sourceTypeDeclarationsRefresher =
274+ yield* SourceTypeDeclarationsRefresherService;
284275 const resolveSecretMaterial = yield* SecretMaterialResolverService;
285276 const sourceAuthMaterialService = yield* RuntimeSourceAuthMaterialService;
286277
@@ -288,6 +279,7 @@ export const RuntimeSourceCatalogSyncLive = Layer.effect( · 1 unmodified line
288279 runtimeLocalWorkspace,
289280 workspaceStateStore,
290281 sourceArtifactStore,
282+ sourceTypeDeclarationsRefresher,
291283 resolveSecretMaterial,
292284 sourceAuthMaterialService,
293285 };
packages/platform/sdk/src/runtime/catalog/source/type-declarations.ts+14−418
@@ -1,427 +1,23 @@
1−import { join } from "node:path";
2−import { FileSystem } from "@effect/platform";
3−import { NodeFileSystem } from "@effect/platform-node";
41 import type { Source } from "#schema";
5−import * as Cause from "effect/Cause";
6−import * as Effect from "effect/Effect";
7−import * as Fiber from "effect/Fiber";
8−
9−import { projectCatalogForAgentSdk } from "@executor/ir/catalog";
102 import type { CatalogSnapshotV1 } from "@executor/ir/model";
11−import {
12− createCatalogTypeProjector,
13− formatPropertyKey,
14− joinTypeNameSegments,
15− projectedCatalogTypeRoots,
16− shapeAllowsOmittedArgs,
17−} from "../catalog-typescript";
18−import type { ResolvedLocalWorkspaceContext } from "../../local/config";
19−import {
20− LocalFileSystemError,
21− unknownLocalErrorDetails,
22−} from "../../local/errors";
3+import * as Context from "effect/Context";
4+import * as Effect from "effect/Effect";
235
24−type SourceDeclarationEntry = {
6+export type BoundSourceDeclarationEntry = {
257 source: Source;
268 snapshot: CatalogSnapshotV1;
279 };
2810
29−type ToolMethodNode = {
30− readonly segments: readonly string[];
31− readonly inputType: string;
32− readonly outputType: string;
33− readonly argsOptional: boolean;
34−};
35−
36−type SourceDeclarationModel = {
37− readonly methods: readonly ToolMethodNode[];
38− readonly supportingTypes: readonly string[];
39−};
40−
41−type ToolTreeNode = {
42− method: ToolMethodNode | null;
43− children: Map<string, ToolTreeNode>;
44−};
45−
46−type SourceDeclarationStub = {
47− sourceId: string;
48−};
49−
50−const GENERATED_TYPES_DIRECTORY = "types";
51−const GENERATED_SOURCE_TYPES_DIRECTORY = "sources";
52−
53−const 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−
61−const declarationDirectory = (context: ResolvedLocalWorkspaceContext): string =>
62− join(context.configDirectory, GENERATED_TYPES_DIRECTORY);
63−
64−const sourceDeclarationDirectory = (context: ResolvedLocalWorkspaceContext): string =>
65− join(declarationDirectory(context), GENERATED_SOURCE_TYPES_DIRECTORY);
66−
67−const sourceDeclarationFileName = (sourceId: string): string => `${sourceId}.d.ts`;
68−
69−const sourceDeclarationPath = (context: ResolvedLocalWorkspaceContext, sourceId: string): string =>
70− join(sourceDeclarationDirectory(context), sourceDeclarationFileName(sourceId));
71−
72−const aggregateDeclarationPath = (context: ResolvedLocalWorkspaceContext): string =>
73− join(declarationDirectory(context), "index.d.ts");
74−
75−const sourceTypeInterfaceName = (sourceId: string): string =>
76− `SourceTools_${sourceId.replace(/[^A-Za-z0-9_$]+/g, "_")}`;
77−
78−const methodSignature = (method: ToolMethodNode): string =>
79− `(${method.argsOptional ? "args?:" : "args:"} ${method.inputType}) => Promise<${method.outputType}>`;
80−
81−const createToolTreeNode = (): ToolTreeNode => ({
82− method: null,
83− children: new Map(),
84−});
85−
86−const 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−
101−const 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−
120−const 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−
143−const 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−
174−const 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−
195−const aggregateDeclarationText = (entries: readonly SourceDeclarationEntry[]): string => {
196− return aggregateDeclarationTextFromSourceIds(
197− entries.map((entry) => ({ sourceId: entry.source.id })),
198− );
199−};
200−
201−const 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−
227−export 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−
280−export 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−
342−export 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−
348−export 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−
355−const 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−
364−const BACKGROUND_DECLARATION_REFRESH_DELAY = "1500 millis";
365−const workspaceRefreshFibers = new Map<string, Fiber.RuntimeFiber<void, never>>();
366−const sourceRefreshFibers = new Map<string, Fiber.RuntimeFiber<void, never>>();
367−
368−export 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− });
11+export type SourceTypeDeclarationsRefresherShape = {
12+ refreshWorkspaceInBackground: (input: {
13+ entries: readonly BoundSourceDeclarationEntry[];
14+ }) => Effect.Effect<void, never, never>;
15+ refreshSourceInBackground: (input: {
16+ source: Source;
17+ snapshot: CatalogSnapshotV1 | null;
18+ }) => Effect.Effect<void, never, never>;
39619 };
39720
398−export 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−};
21+export class SourceTypeDeclarationsRefresherService extends Context.Tag(
22+ "#runtime/SourceTypeDeclarationsRefresherService",
23+)<SourceTypeDeclarationsRefresherService, SourceTypeDeclarationsRefresherShape>() {}
packages/platform/sdk/src/runtime/control-plane-runtime.test.ts+9−11
@@ -31,28 +31,26 @@ import { · 30 unmodified lines
3131 provideControlPlaneRuntime,
3232 RuntimeExecutionResolverService,
3333 } from "./index";
34−import { createLocalControlPlaneRuntime as createControlPlaneRuntime } from "../../../sdk-file/src/index";
3534 import { createSourceFromPayload } from "./sources/source-definitions";
3635 import { decodeSourceCredentialSelectionContent } from "./sources/source-credential-interactions";
3736 import { persistSource } from "./sources/source-store";
3837 import { withControlPlaneClient } from "./execution/test-http-client";
3938 import { runtimeEffectError } from "./effect-errors";
4039 import {
40+ buildLocalSourceArtifact,
41+ createLocalControlPlaneRuntime as createControlPlaneRuntime,
42+ deriveLocalInstallation,
43+ readLocalSourceArtifact,
4144 resolveLocalWorkspaceContext,
45+ writeLocalControlPlaneState,
46+ writeLocalSourceArtifact,
4247 writeProjectLocalExecutorConfig,
43−} from "./local/config";
44−import { writeLocalControlPlaneState } from "./local/control-plane-store";
45−import { deriveLocalInstallation } from "./local/installation";
48+ writeLocalWorkspaceState,
49+} from "../../../sdk-file/src/index";
4650 import {
4751 decodeSourceCredentialInteraction,
4852 submitSourceCredentialInteraction,
49−} from "./local/operations";
50−import {
51− buildLocalSourceArtifact,
52− readLocalSourceArtifact,
53− writeLocalSourceArtifact,
54−} from "./local/source-artifacts";
55−import { writeLocalWorkspaceState } from "./local/workspace-state";
53+} from "../local/operations";
5654
5755 const makeRuntime = Effect.gen(function* () {
5856 const fs = yield* FileSystem.FileSystem;
packages/platform/sdk/src/runtime/execution/service.ts+1−1
@@ -38,7 +38,7 @@ import { · 37 unmodified lines
3838 import {
3939 getRuntimeLocalWorkspaceOption,
4040 provideOptionalRuntimeLocalWorkspace,
41−} from "../local/runtime-context";
41+} from "../workspace/runtime-context";
4242 import {
4343 asOperationErrors,
4444 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
55 import * as Schema from "effect/Schema";
66
77 import type { LoadedSourceCatalogToolIndexEntry } from "../../catalog/source/runtime";
8−import type { SecretMaterialResolveContext } from "../../local/secret-material-providers";
9−import type { WorkspaceStorageServices } from "../../local/storage";
8+import type { SecretMaterialResolveContext } from "../../workspace/secret-material-providers";
9+import type { WorkspaceStorageServices } from "../../workspace/storage";
1010 import { invocationDescriptorFromTool } from "../ir-execution";
1111 import { evaluateInvocationPolicy } from "../../policy/invocation-policy-engine";
12−import { loadRuntimeLocalWorkspacePolicies } from "../../policy/policies-operations";
13−import { runtimeEffectError } from "../../effect-errors";
12+import { loadRuntimeLocalWorkspacePolicies } from "../../../policies/operations";
1413
1514 const asToolPath = (value: string): ToolPath => value as ToolPath;
1615
@@ -62,6 +61,9 @@ export const toSecretResolutionContext = ( · 45 unmodified lines
6261 };
6362 };
6463
64+const failAuthorization = (message: string): Effect.Effect<never, Error, never> =>
65+ Effect.fail(new Error(message));
66+
6567 type WorkspaceToolElicitation = Parameters<
6668 typeof makeToolInvokerFromTools
6769 >[0]["onElicitation"];
@@ -99,13 +101,13 @@ export const authorizePersistedToolInvocation = (input: { · 31 unmodified lines
99101 }
100102
101103 if (decision.kind === "deny") {
102− return yield* runtimeEffectError("execution/workspace/authorization", decision.reason);
104+ return yield* failAuthorization(decision.reason);
103105 }
104106
105107 if (!input.onElicitation) {
106− return yield* runtimeEffectError("execution/workspace/authorization",
107− `Approval required for ${descriptor.toolPath}, but no elicitation-capable host is available`,
108− );
108+ return yield* failAuthorization(
109+ `Approval required for ${descriptor.toolPath}, but no elicitation-capable host is available`,
110+ );
109111 }
110112
111113 const interactionId =
@@ -144,8 +146,8 @@ export const authorizePersistedToolInvocation = (input: { · 32 unmodified lines
144146 );
145147
146148 if (response.action !== "accept") {
147− return yield* runtimeEffectError("execution/workspace/authorization",
148− `Tool invocation not approved for ${descriptor.toolPath}`,
149− );
149+ return yield* failAuthorization(
150+ `Tool invocation not approved for ${descriptor.toolPath}`,
151+ );
150152 }
151153 });
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
1919 import { RuntimeSourceCatalogStoreService } from "../../catalog/source/runtime";
2020 import { RuntimeSourceAuthMaterialService } from "../../auth/source-auth-material";
2121 import { RuntimeSourceCatalogSyncService } from "../../catalog/source/sync";
22−import { getRuntimeLocalWorkspaceOption } from "../../local/runtime-context";
22+import { getRuntimeLocalWorkspaceOption } from "../../workspace/runtime-context";
23+import {
24+ LocalInstanceConfigService,
25+ SecretMaterialDeleterService,
26+ SecretMaterialStorerService,
27+ SecretMaterialUpdaterService,
28+} from "../../workspace/secret-material-providers";
2329 import {
2430 LocalToolRuntimeLoaderService,
2531 type LocalToolRuntimeLoaderShape,
2632 type LocalToolRuntime,
27−} from "../../local/tools";
33+} from "../../local-tool-runtime";
2834 import {
2935 InstallationStore,
3036 type InstallationStoreShape,
@@ -34,7 +40,7 @@ import { · 3 unmodified lines
3440 type WorkspaceConfigStoreShape,
3541 WorkspaceStateStore,
3642 type WorkspaceStateStoreShape,
37−} from "../../local/storage";
43+} from "../../workspace/storage";
3844 import { ControlPlaneStore } from "../../store";
3945 import { RuntimeSourceStoreService } from "../../sources/source-store";
4046 import type { CreateWorkspaceInternalToolMap } from "./tool-invoker";
@@ -68,6 +74,10 @@ export const createWorkspaceExecutionEnvironmentResolver = · 27 unmodified lines
6874 >;
6975 localToolRuntimeLoader: LocalToolRuntimeLoaderShape;
7076 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>;
7181 workspaceConfigStore: WorkspaceConfigStoreShape;
7282 workspaceStateStore: WorkspaceStateStoreShape;
7383 sourceArtifactStore: SourceArtifactStoreShape;
@@ -79,15 +89,11 @@ export const createWorkspaceExecutionEnvironmentResolver = · 5 unmodified lines
7989 const loadedConfig =
8090 runtimeLocalWorkspace === null
8191 ? null
82− : yield* input.workspaceConfigStore.load(
83− runtimeLocalWorkspace.context,
84− );
92+ : yield* input.workspaceConfigStore.load();
8593 const localToolRuntime =
8694 runtimeLocalWorkspace === null
8795 ? createEmptyLocalToolRuntime()
88− : yield* input.localToolRuntimeLoader.load(
89− runtimeLocalWorkspace.context,
90− );
96+ : yield* input.localToolRuntimeLoader.load();
9197 const { catalog, toolInvoker } = createWorkspaceToolInvoker({
9298 workspaceId,
9399 accountId,
@@ -96,6 +102,10 @@ export const createWorkspaceExecutionEnvironmentResolver = · 2 unmodified lines
96102 sourceCatalogSyncService: input.sourceCatalogSyncService,
97103 sourceCatalogStore: input.sourceCatalogStore,
98104 installationStore: input.installationStore,
105+ instanceConfigResolver: input.instanceConfigResolver,
106+ storeSecretMaterial: input.storeSecretMaterial,
107+ deleteSecretMaterial: input.deleteSecretMaterial,
108+ updateSecretMaterial: input.updateSecretMaterial,
99109 workspaceConfigStore: input.workspaceConfigStore,
100110 workspaceStateStore: input.workspaceStateStore,
101111 sourceArtifactStore: input.sourceArtifactStore,
@@ -146,6 +156,10 @@ export const RuntimeExecutionResolverLive = ( · 44 unmodified lines
146156 const sourceCatalogStore = yield* RuntimeSourceCatalogStoreService;
147157 const localToolRuntimeLoader = yield* LocalToolRuntimeLoaderService;
148158 const installationStore = yield* InstallationStore;
159+ const instanceConfigResolver = yield* LocalInstanceConfigService;
160+ const storeSecretMaterial = yield* SecretMaterialStorerService;
161+ const deleteSecretMaterial = yield* SecretMaterialDeleterService;
162+ const updateSecretMaterial = yield* SecretMaterialUpdaterService;
149163 const workspaceConfigStore = yield* WorkspaceConfigStore;
150164 const workspaceStateStore = yield* WorkspaceStateStore;
151165 const sourceArtifactStore = yield* SourceArtifactStore;
@@ -159,6 +173,10 @@ export const RuntimeExecutionResolverLive = ( · 7 unmodified lines
159173 sourceCatalogStore,
160174 localToolRuntimeLoader,
161175 installationStore,
176+ instanceConfigResolver,
177+ storeSecretMaterial,
178+ deleteSecretMaterial,
179+ updateSecretMaterial,
162180 workspaceConfigStore,
163181 workspaceStateStore,
164182 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
33 import {
44 provideOptionalRuntimeLocalWorkspace,
55 type RuntimeLocalWorkspaceState,
6−} from "../../local/runtime-context";
6+} from "../../workspace/runtime-context";
77
88 export const provideRuntimeLocalWorkspace = <A, E, R>(
99 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
1010 type LoadedSourceCatalogToolIndexEntry,
1111 catalogToolCatalogEntry,
1212 } from "../../catalog/source/runtime";
13−import type { RuntimeLocalWorkspaceState } from "../../local/runtime-context";
13+import type { RuntimeLocalWorkspaceState } from "../../workspace/runtime-context";
1414 import {
1515 makeWorkspaceStorageLayer,
1616 type SourceArtifactStoreShape,
1717 type WorkspaceConfigStoreShape,
1818 type WorkspaceStateStoreShape,
1919 type WorkspaceStorageServices,
20−} from "../../local/storage";
20+} from "../../workspace/storage";
2121 import { provideRuntimeLocalWorkspace } from "./local";
2222
2323 const 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
1313
1414 import { RuntimeSourceAuthMaterialService } from "../../auth/source-auth-material";
1515 import { RuntimeSourceCatalogStoreService } from "../../catalog/source/runtime";
16−import type { RuntimeLocalWorkspaceState } from "../../local/runtime-context";
17−import { type LocalToolRuntime } from "../../local/tools";
16+import type { RuntimeLocalWorkspaceState } from "../../workspace/runtime-context";
17+import { type LocalToolRuntime } from "../../local-tool-runtime";
1818 import {
1919 type InstallationStoreShape,
2020 makeWorkspaceStorageLayer,
2121 type SourceArtifactStoreShape,
2222 type WorkspaceConfigStoreShape,
2323 type WorkspaceStateStoreShape,
24−} from "../../local/storage";
24+} from "../../workspace/storage";
25+import type {
26+ DeleteSecretMaterial,
27+ ResolveInstanceConfig,
28+ StoreSecretMaterial,
29+ UpdateSecretMaterial,
30+} from "../../workspace/secret-material-providers";
2531 import type { ControlPlaneStoreShape } from "../../store";
2632 import { RuntimeSourceAuthService } from "../../sources/source-auth-service";
2733 import { type RuntimeSourceStore } from "../../sources/source-store";
@@ -49,6 +55,10 @@ export type WorkspaceInternalToolContext = { · 21 unmodified lines
4955 >;
5056 sourceAuthService: RuntimeSourceAuthService;
5157 installationStore: InstallationStoreShape;
58+ instanceConfigResolver: ResolveInstanceConfig;
59+ storeSecretMaterial: StoreSecretMaterial;
60+ deleteSecretMaterial: DeleteSecretMaterial;
61+ updateSecretMaterial: UpdateSecretMaterial;
5262 workspaceConfigStore: WorkspaceConfigStoreShape;
5363 workspaceStateStore: WorkspaceStateStoreShape;
5464 sourceArtifactStore: SourceArtifactStoreShape;
@@ -71,6 +81,10 @@ export const createWorkspaceToolInvoker = (input: { · 16 unmodified lines
7181 typeof RuntimeSourceCatalogStoreService
7282 >;
7383 installationStore: InstallationStoreShape;
84+ instanceConfigResolver: ResolveInstanceConfig;
85+ storeSecretMaterial: StoreSecretMaterial;
86+ deleteSecretMaterial: DeleteSecretMaterial;
87+ updateSecretMaterial: UpdateSecretMaterial;
7488 workspaceConfigStore: WorkspaceConfigStoreShape;
7589 workspaceStateStore: WorkspaceStateStoreShape;
7690 sourceArtifactStore: SourceArtifactStoreShape;
@@ -115,6 +129,10 @@ export const createWorkspaceToolInvoker = (input: { · 38 unmodified lines
115129 sourceCatalogSyncService: input.sourceCatalogSyncService,
116130 sourceAuthService: input.sourceAuthService,
117131 installationStore: input.installationStore,
132+ instanceConfigResolver: input.instanceConfigResolver,
133+ storeSecretMaterial: input.storeSecretMaterial,
134+ deleteSecretMaterial: input.deleteSecretMaterial,
135+ updateSecretMaterial: input.updateSecretMaterial,
118136 workspaceConfigStore: input.workspaceConfigStore,
119137 workspaceStateStore: input.workspaceStateStore,
120138 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
1313 import { RuntimeSourceCatalogStoreLive } from "./catalog/source/runtime";
1414 import { reconcileMissingSourceCatalogArtifacts } from "./catalog/source/reconcile";
1515 import { RuntimeSourceCatalogSyncLive } from "./catalog/source/sync";
16+import {
17+ SourceTypeDeclarationsRefresherService,
18+ type SourceTypeDeclarationsRefresherShape,
19+} from "./catalog/source/type-declarations";
1620 import { createLiveExecutionManager, LiveExecutionManagerService } from "./execution/live";
1721 import { RuntimeExecutionResolverLive } from "./execution/workspace/environment";
1822 import type { CreateWorkspaceInternalToolMap, WorkspaceInternalToolContext } from "./execution/workspace/tool-invoker";
19−import type {
20− LoadedLocalExecutorConfig,
21− ResolvedLocalWorkspaceContext,
22−} from "./local/config";
23+import type { LoadedLocalExecutorConfig } from "./workspace-config";
2324 import type { LocalExecutorConfig } from "#schema";
24−import type { LocalControlPlanePersistence } from "./local/control-plane-store";
25+import type { InstanceConfig } from "../local/contracts";
26+import type {
27+ ExecutorWorkspaceContext,
28+ ExecutorWorkspaceDescriptor,
29+} from "../workspace";
2530 import {
31+ LocalInstanceConfigService,
2632 type DeleteSecretMaterial,
2733 type ResolveSecretMaterial,
2834 SecretMaterialDeleterService,
@@ -31,17 +37,17 @@ import { · 2 unmodified lines
3137 SecretMaterialUpdaterService,
3238 type StoreSecretMaterial,
3339 type UpdateSecretMaterial,
34−} from "./local/secret-material-providers";
35−import type { LocalSourceArtifact } from "./local/source-artifacts";
40+} from "./workspace/secret-material-providers";
41+import type { LocalSourceArtifact } from "./source-artifacts";
3642 import {
3743 type RuntimeLocalWorkspaceState,
3844 RuntimeLocalWorkspaceLive,
39−} from "./local/runtime-context";
45+} from "./workspace/runtime-context";
4046 import {
4147 type LocalToolRuntime,
4248 type LocalToolRuntimeLoaderShape,
4349 LocalToolRuntimeLoaderService,
44−} from "./local/tools";
50+} from "./local-tool-runtime";
4551 import {
4652 InstallationStore,
4753 makeLocalStorageLayer,
@@ -49,9 +55,9 @@ import { · 1 unmodified line
4955 type SourceArtifactStoreShape,
5056 type WorkspaceConfigStoreShape,
5157 type WorkspaceStateStoreShape,
52−} from "./local/storage";
53−import type { LocalWorkspaceState } from "./local/workspace-state";
54−import { synchronizeLocalWorkspaceState } from "./local/workspace-sync";
58+} from "./workspace/storage";
59+import type { LocalWorkspaceState } from "./workspace-state";
60+import { synchronizeLocalWorkspaceState } from "./workspace/workspace-sync";
5561 import { ControlPlaneStore, type ControlPlaneStoreShape } from "./store";
5662 import { RuntimeSourceAuthServiceLive } from "./sources/source-auth-service";
5763 import { RuntimeSourceStoreLive } from "./sources/source-store";
@@ -59,19 +65,10 @@ import { RuntimeSourceStoreLive } from "./sources/source-store"; · 1 unmodified line
5965 export * from "./execution/state";
6066 export * from "./sources/executor-tools";
6167 export * from "./execution/live";
62−export * from "./local/config";
63−export * from "./local/control-plane-store";
64−export * from "./local/errors";
65−export * from "./local/installation";
66−export * from "./local/runtime-context";
67−export * from "./local/storage";
68−export * from "./local/source-artifacts";
69−export * from "./local/tools";
7068 export * from "./catalog/schema-type-signature";
7169 export * from "./catalog/source/runtime";
7270 export * from "./catalog/source/sync";
7371 export * from "./sources/source-auth-service";
74−export * from "./local/secret-material-providers";
7572 export * from "./sources/source-credential-interactions";
7673 export * from "./sources/source-adapters/mcp";
7774 export * from "./sources/source-store";
@@ -84,6 +81,20 @@ export type { · 6 unmodified lines
8481 CreateWorkspaceInternalToolMap,
8582 WorkspaceInternalToolContext,
8683 } from "./execution/workspace/tool-invoker";
84+export {
85+ LocalInstanceConfigService,
86+ SecretMaterialDeleterService,
87+ SecretMaterialResolverService,
88+ SecretMaterialStorerService,
89+ SecretMaterialUpdaterService,
90+} from "./workspace/secret-material-providers";
91+export type {
92+ DeleteSecretMaterial,
93+ ResolveInstanceConfig,
94+ ResolveSecretMaterial,
95+ StoreSecretMaterial,
96+ UpdateSecretMaterial,
97+} from "./workspace/secret-material-providers";
8798 export {
8899 builtInSourceAdapters,
89100 connectableSourceAdapters,
@@ -154,6 +165,9 @@ export type BoundLocalToolRuntimeLoader = { · 64 unmodified lines
154165 load: () => ReturnType<LocalToolRuntimeLoaderShape["load"]>;
155166 };
156167
168+export type BoundSourceTypeDeclarationsRefresher =
169+ SourceTypeDeclarationsRefresherShape;
170+
157171 export type RuntimeSecretMaterialServices = {
158172 resolve: ResolveSecretMaterial;
159173 store: StoreSecretMaterial;
@@ -161,15 +175,26 @@ export type RuntimeSecretMaterialServices = { · 1 unmodified line
161175 update: UpdateSecretMaterial;
162176 };
163177
178+export type RuntimeInstanceConfigService = {
179+ resolve: () => Effect.Effect<InstanceConfig, Error, never>;
180+};
181+
182+export type RuntimePersistence = {
183+ rows: ControlPlaneStoreShape;
184+ close: () => Promise<void>;
185+};
186+
164187 export type RuntimeControlPlaneServices = {
165− workspaceContext: ResolvedLocalWorkspaceContext;
188+ workspace: ExecutorWorkspaceDescriptor;
166189 installationStore: BoundInstallationStore;
167190 workspaceConfigStore: BoundWorkspaceConfigStore;
168191 workspaceStateStore: BoundWorkspaceStateStore;
169192 sourceArtifactStore: BoundSourceArtifactStore;
170193 localToolRuntimeLoader?: BoundLocalToolRuntimeLoader;
194+ sourceTypeDeclarationsRefresher?: BoundSourceTypeDeclarationsRefresher;
171195 secretMaterial: RuntimeSecretMaterialServices;
172− persistence: LocalControlPlanePersistence;
196+ instanceConfig: RuntimeInstanceConfigService;
197+ persistence: RuntimePersistence;
173198 };
174199
175200 const emptyToolRuntime = (): LocalToolRuntime => {
@@ -182,17 +207,22 @@ const emptyToolRuntime = (): LocalToolRuntime => { · 6 unmodified lines
182207 };
183208 };
184209
210+const noopSourceTypeDeclarationsRefresher: BoundSourceTypeDeclarationsRefresher = {
211+ refreshWorkspaceInBackground: () => Effect.void,
212+ refreshSourceInBackground: () => Effect.void,
213+};
214+
185215 const toInstallationStoreShape = (
186216 input: BoundInstallationStore,
187217 ): InstallationStoreShape => ({
188− load: () => input.load(),
189− getOrProvision: () => input.getOrProvision(),
218+ load: input.load,
219+ getOrProvision: input.getOrProvision,
190220 });
191221
192222 const toWorkspaceConfigStoreShape = (
193223 input: BoundWorkspaceConfigStore,
194224 ): WorkspaceConfigStoreShape => ({
195− load: () => input.load(),
225+ load: input.load,
196226 writeProject: ({ config }) => input.writeProject(config),
197227 resolveRelativePath: input.resolveRelativePath,
198228 });
@@ -200,7 +230,7 @@ const toWorkspaceConfigStoreShape = ( · 1 unmodified line
200230 const toWorkspaceStateStoreShape = (
201231 input: BoundWorkspaceStateStore,
202232 ): WorkspaceStateStoreShape => ({
203− load: () => input.load(),
233+ load: input.load,
204234 write: ({ state }) => input.write(state),
205235 });
206236
@@ -221,6 +251,9 @@ const makeSecretMaterialLayer = (input: RuntimeSecretMaterialServices) => · 14 unmodified lines
221251 Layer.succeed(SecretMaterialUpdaterService, input.update),
222252 );
223253
254+const makeInstanceConfigLayer = (input: RuntimeInstanceConfigService) =>
255+ Layer.succeed(LocalInstanceConfigService, input.resolve);
256+
224257 export const createRuntimeControlPlaneLayer = (
225258 input: RuntimeControlPlaneOptions & RuntimeControlPlaneServices & {
226259 store: ControlPlaneStoreShape;
@@ -241,19 +274,30 @@ export const createRuntimeControlPlaneLayer = ( · 14 unmodified lines
241274 input.localToolRuntimeLoader?.load() ?? Effect.succeed(emptyToolRuntime()),
242275 }),
243276 );
277+ const sourceTypeDeclarationsRefresherLayer = Layer.succeed(
278+ SourceTypeDeclarationsRefresherService,
279+ SourceTypeDeclarationsRefresherService.of(
280+ input.sourceTypeDeclarationsRefresher
281+ ?? noopSourceTypeDeclarationsRefresher,
282+ ),
283+ );
244284
245285 const baseLayer = Layer.mergeAll(
246286 Layer.succeed(ControlPlaneStore, input.store),
247287 RuntimeLocalWorkspaceLive(input.localWorkspaceState),
248288 storageLayer,
249289 Layer.succeed(LiveExecutionManagerService, input.liveExecutionManager),
290+ sourceTypeDeclarationsRefresherLayer,
250291 );
251292
252293 const secretMaterialLayer = makeSecretMaterialLayer(input.secretMaterial).pipe(
253294 Layer.provide(baseLayer),
254295 );
296+ const instanceConfigLayer = makeInstanceConfigLayer(input.instanceConfig);
255297
256− const sourceStoreLayer = RuntimeSourceStoreLive.pipe(Layer.provide(baseLayer));
298+ const sourceStoreLayer = RuntimeSourceStoreLive.pipe(
299+ Layer.provide(Layer.mergeAll(baseLayer, secretMaterialLayer)),
300+ );
257301
258302 const sourceCatalogStoreLayer = RuntimeSourceCatalogStoreLive.pipe(
259303 Layer.provide(Layer.mergeAll(baseLayer, sourceStoreLayer)),
@@ -275,6 +319,7 @@ export const createRuntimeControlPlaneLayer = ( · 15 unmodified lines
275319 Layer.provide(
276320 Layer.mergeAll(
277321 baseLayer,
322+ instanceConfigLayer,
278323 secretMaterialLayer,
279324 sourceStoreLayer,
280325 sourceCatalogSyncLayer,
@@ -289,6 +334,7 @@ export const createRuntimeControlPlaneLayer = ( · 8 unmodified lines
289334 Layer.provide(
290335 Layer.mergeAll(
291336 baseLayer,
337+ instanceConfigLayer,
292338 secretMaterialLayer,
293339 sourceStoreLayer,
294340 sourceAuthMaterialLayer,
@@ -302,6 +348,7 @@ export const createRuntimeControlPlaneLayer = ( · 7 unmodified lines
302348
303349 return Layer.mergeAll(
304350 baseLayer,
351+ instanceConfigLayer,
305352 secretMaterialLayer,
306353 sourceStoreLayer,
307354 sourceAuthMaterialLayer,
@@ -314,7 +361,7 @@ export const createRuntimeControlPlaneLayer = ( · 6 unmodified lines
314361 };
315362
316363 export type ControlPlaneRuntime = {
317− persistence: LocalControlPlanePersistence;
364+ persistence: RuntimePersistence;
318365 localInstallation: LocalInstallation;
319366 managedRuntime: ManagedRuntime.ManagedRuntime<any, never>;
320367 runtimeLayer: RuntimeControlPlaneLayer;
@@ -339,8 +386,12 @@ export const createControlPlaneRuntimeFromServices = (input: { · 18 unmodified lines
339386 .load()
340387 .pipe(Effect.mapError(toRuntimeBootstrapError));
341388
389+ const runtimeWorkspace: ExecutorWorkspaceContext = {
390+ ...input.services.workspace,
391+ workspaceId: localInstallation.workspaceId,
392+ accountId: localInstallation.accountId,
393+ };
342394 const effectiveLocalConfig = yield* synchronizeLocalWorkspaceState({
343− context: input.services.workspaceContext,
344395 loadedConfig: loadedLocalConfig,
345396 })
346397 .pipe(
@@ -362,9 +413,8 @@ export const createControlPlaneRuntimeFromServices = (input: { · 15 unmodified lines
362413 ),
363414 )
364415 .pipe(Effect.mapError(toRuntimeBootstrapError));
365−
366416 const runtimeLocalWorkspaceState: RuntimeLocalWorkspaceState = {
367− context: input.services.workspaceContext,
417+ workspace: runtimeWorkspace,
368418 installation: {
369419 workspaceId: localInstallation.workspaceId,
370420 accountId: localInstallation.accountId,
packages/platform/sdk/src/runtime/local-tool-runtime.ts+37−0
@@ -0,0 +1,37 @@
1+import type {
2+ ToolCatalog,
3+ ToolInvoker,
4+ ToolMap,
5+} from "@executor/codemode-core";
6+import * as Context from "effect/Context";
7+import * as Effect from "effect/Effect";
8+import * as Layer from "effect/Layer";
9+
10+export type LocalToolRuntime = {
11+ tools: ToolMap;
12+ catalog: ToolCatalog;
13+ toolInvoker: ToolInvoker;
14+ toolPaths: Set<string>;
15+};
16+
17+export type LocalToolRuntimeLoaderShape = {
18+ load: () => Effect.Effect<LocalToolRuntime, Error, never>;
19+};
20+
21+export class LocalToolRuntimeLoaderService extends Context.Tag(
22+ "#runtime/LocalToolRuntimeLoaderService",
23+)<LocalToolRuntimeLoaderService, LocalToolRuntimeLoaderShape>() {}
24+
25+export 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 @@
1−export * from "../../local/operations";
packages/platform/sdk/src/runtime/local/source-artifacts.ts+0−377
@@ -1,377 +0,0 @@
1−import { join } from "node:path";
2−import { FileSystem } from "@effect/platform";
3−
4−import {
5− SourceIdSchema,
6− SourceCatalogIdSchema,
7− StoredSourceCatalogRevisionRecordSchema,
8− TimestampMsSchema,
9− type Source,
10− type SourceCatalogId,
11−} from "#schema";
12−import * as Effect from "effect/Effect";
13−import * as Option from "effect/Option";
14−import * as Schema from "effect/Schema";
15−
16−import { decodeCatalogSnapshotV1 } from "@executor/ir/catalog";
17−import {
18− CatalogSnapshotV1Schema,
19− type CatalogSnapshotV1,
20− type NativeBlob,
21− type SourceDocument,
22−} from "@executor/ir/model";
23−import {
24− contentHash,
25− snapshotFromSourceCatalogSyncResult,
26− type SourceCatalogSyncResult,
27−} from "@executor/source-core";
28−import type { ResolvedLocalWorkspaceContext } from "./config";
29−import {
30− LocalFileSystemError,
31− unknownLocalErrorDetails,
32−} from "./errors";
33−import {
34− createSourceCatalogRevisionRecord,
35− stableSourceCatalogId,
36−} from "../sources/source-definitions";
37−const LEGACY_LOCAL_SOURCE_ARTIFACT_VERSION = 3 as const;
38−const LOCAL_SOURCE_ARTIFACT_VERSION = 4 as const;
39−
40−const 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−
49−export 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−
58−export type LocalSourceArtifact = typeof LocalSourceArtifactSchema.Type;
59−type LegacyLocalSourceArtifact = typeof LegacyLocalSourceArtifactSchema.Type;
60−
61−const 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−
70−const 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−
79−type ReadableLegacyLocalSourceArtifact =
80− typeof ReadableLegacyLocalSourceArtifactSchema.Type;
81−type ReadableLocalSourceArtifact =
82− typeof ReadableLocalSourceArtifactSchema.Type;
83−
84−const decodeReadableLocalSourceArtifactOption = Schema.decodeUnknownOption(
85− Schema.parseJson(
86− Schema.Union(
87− ReadableLocalSourceArtifactSchema,
88− ReadableLegacyLocalSourceArtifactSchema,
89− ),
90− ),
91−);
92−
93−const 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−
103−const 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−
111−const mutableRecord = <K extends string, V>(value: Readonly<Record<K, V>>): Record<K, V> =>
112− value as Record<K, V>;
113−
114−const localSourceArtifactPath = (input: {
115− context: ResolvedLocalWorkspaceContext;
116− sourceId: string;
117−}): string =>
118− join(
119− input.context.artifactsDirectory,
120− "sources",
121− `${input.sourceId}.json`,
122− );
123−
124−const localSourceDocumentDirectory = (input: {
125− context: ResolvedLocalWorkspaceContext;
126− sourceId: string;
127−}): string =>
128− join(input.context.artifactsDirectory, "sources", input.sourceId, "documents");
129−
130−const localSourceDocumentPath = (input: {
131− context: ResolvedLocalWorkspaceContext;
132− sourceId: string;
133− documentId: string;
134−}): string =>
135− join(localSourceDocumentDirectory(input), `${input.documentId}.txt`);
136−
137−const 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−
181−const 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−
201−const snapshotHash = (snapshot: CatalogSnapshotV1): string =>
202− contentHash(JSON.stringify(snapshot));
203−
204−const importMetadataHash = (snapshot: { import: SourceCatalogSyncResult["importMetadata"] }): string =>
205− contentHash(JSON.stringify(snapshot.import));
206−
207−const decodeCatalogSnapshotV1Option = (
208− snapshot: unknown,
209−): CatalogSnapshotV1 | null => {
210− try {
211− return decodeCatalogSnapshotV1(snapshot);
212− } catch {
213− return null;
214− }
215−};
216−
217−export 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−
245−export 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−
321−export 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−
358−export 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 @@
1−import { FileSystem } from "@effect/platform";
2−import { NodeFileSystem } from "@effect/platform-node";
3−import type { LocalInstallation, LocalExecutorConfig } from "#schema";
4−import * as Context from "effect/Context";
5−import * as Effect from "effect/Effect";
6−import * as Layer from "effect/Layer";
7−
8−import type {
9− LoadedLocalExecutorConfig,
10− ResolvedLocalWorkspaceContext,
11−} from "./config";
12−import {
13− loadLocalExecutorConfig,
14− resolveConfigRelativePath,
15− writeProjectLocalExecutorConfig,
16−} from "./config";
17−import {
18− getOrProvisionLocalInstallation,
19− loadLocalInstallation,
20−} from "./installation";
21−import type {
22− LocalSourceArtifact,
23−} from "./source-artifacts";
24−import {
25− buildLocalSourceArtifact,
26− readLocalSourceArtifact,
27− removeLocalSourceArtifact,
28− writeLocalSourceArtifact,
29−} from "./source-artifacts";
30−import type { LocalWorkspaceState } from "./workspace-state";
31−import {
32− loadLocalWorkspaceState,
33− writeLocalWorkspaceState,
34−} from "./workspace-state";
35−import type { SourceCatalogSyncResult } from "@executor/source-core";
36−import type { Source } from "#schema";
37−
38−export 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−
47−export class InstallationStore extends Context.Tag(
48− "#runtime/InstallationStore",
49−)<InstallationStore, InstallationStoreShape>() {}
50−
51−export 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−
62−export class WorkspaceConfigStore extends Context.Tag(
63− "#runtime/WorkspaceConfigStore",
64−)<WorkspaceConfigStore, WorkspaceConfigStoreShape>() {}
65−
66−export 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−
76−export class WorkspaceStateStore extends Context.Tag(
77− "#runtime/WorkspaceStateStore",
78−)<WorkspaceStateStore, WorkspaceStateStoreShape>() {}
79−
80−export 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−
100−export class SourceArtifactStore extends Context.Tag(
101− "#runtime/SourceArtifactStore",
102−)<SourceArtifactStore, SourceArtifactStoreShape>() {}
103−
104−export type LocalStorageServices =
105− | InstallationStore
106− | WorkspaceConfigStore
107− | WorkspaceStateStore
108− | SourceArtifactStore;
109−
110−export type WorkspaceStorageServices =
111− | WorkspaceConfigStore
112− | WorkspaceStateStore
113− | SourceArtifactStore;
114−
115−export const LocalInstallationStore: InstallationStoreShape = {
116− load: loadLocalInstallation,
117− getOrProvision: getOrProvisionLocalInstallation,
118−};
119−
120−export const LocalInstallationStoreLive = Layer.succeed(
121− InstallationStore,
122− LocalInstallationStore,
123−);
124−
125−const 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−
131−const 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−
136−export const LocalWorkspaceConfigStore: WorkspaceConfigStoreShape = {
137− load: (context) => bindNodeFileSystem(loadLocalExecutorConfig(context)),
138− writeProject: (input) => bindNodeFileSystem(writeProjectLocalExecutorConfig(input)),
139− resolveRelativePath: resolveConfigRelativePath,
140−};
141−
142−export 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−
156−export const LocalWorkspaceStateStore: WorkspaceStateStoreShape = {
157− load: (context) => bindNodeFileSystem(loadLocalWorkspaceState(context)),
158− write: (input) => bindNodeFileSystem(writeLocalWorkspaceState(input)),
159−};
160−
161−export 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−
173−export 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−
180−export 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−
194−export 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−
205−export 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−
216−export const WorkspaceStorageLive = Layer.mergeAll(
217− LocalWorkspaceConfigStoreLive,
218− LocalWorkspaceStateStoreLive,
219− LocalSourceArtifactStoreLive,
220−);
221−
222−export const LocalStorageLive = Layer.mergeAll(
223− LocalInstallationStoreLive,
224− WorkspaceStorageLive,
225−);
packages/platform/sdk/src/runtime/policy/policies-operations.ts+0−1
@@ -1 +0,0 @@
1−export * from "../../policies/operations";
packages/platform/sdk/src/runtime/source-artifacts.ts+234−0
@@ -0,0 +1,234 @@
1+import {
2+ SourceIdSchema,
3+ SourceCatalogIdSchema,
4+ StoredSourceCatalogRevisionRecordSchema,
5+ TimestampMsSchema,
6+ type Source,
7+ type SourceCatalogId,
8+} from "#schema";
9+import * as Option from "effect/Option";
10+import * as Schema from "effect/Schema";
11+
12+import { decodeCatalogSnapshotV1 } from "@executor/ir/catalog";
13+import {
14+ CatalogSnapshotV1Schema,
15+ type CatalogSnapshotV1,
16+ type NativeBlob,
17+ type SourceDocument,
18+} from "@executor/ir/model";
19+import {
20+ contentHash,
21+ snapshotFromSourceCatalogSyncResult,
22+ type SourceCatalogSyncResult,
23+} from "@executor/source-core";
24+import {
25+ createSourceCatalogRevisionRecord,
26+ stableSourceCatalogId,
27+} from "./sources/source-definitions";
28+
29+const LEGACY_LOCAL_SOURCE_ARTIFACT_VERSION = 3 as const;
30+const LOCAL_SOURCE_ARTIFACT_VERSION = 4 as const;
31+
32+const 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+
41+export 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+
50+export type LocalSourceArtifact = typeof LocalSourceArtifactSchema.Type;
51+type LegacyLocalSourceArtifact = typeof LegacyLocalSourceArtifactSchema.Type;
52+
53+const 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+
62+const 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+
71+type ReadableLegacyLocalSourceArtifact =
72+ typeof ReadableLegacyLocalSourceArtifactSchema.Type;
73+type ReadableLocalSourceArtifact =
74+ typeof ReadableLocalSourceArtifactSchema.Type;
75+
76+const decodeReadableLocalSourceArtifactOption = Schema.decodeUnknownOption(
77+ Schema.parseJson(
78+ Schema.Union(
79+ ReadableLocalSourceArtifactSchema,
80+ ReadableLegacyLocalSourceArtifactSchema,
81+ ),
82+ ),
83+);
84+
85+const 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+
95+const mutableRecord = <K extends string, V>(
96+ value: Readonly<Record<K, V>>,
97+): Record<K, V> => value as Record<K, V>;
98+
99+export type SourceArtifactDocumentContent = {
100+ documentId: string;
101+ blob: NativeBlob;
102+ content: string;
103+};
104+
105+export 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+
147+export 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+
171+const snapshotHash = (snapshot: CatalogSnapshotV1): string =>
172+ contentHash(JSON.stringify(snapshot));
173+
174+const importMetadataHash = (snapshot: {
175+ import: SourceCatalogSyncResult["importMetadata"];
176+}): string => contentHash(JSON.stringify(snapshot.import));
177+
178+const decodeCatalogSnapshotV1Option = (
179+ snapshot: unknown,
180+): CatalogSnapshotV1 | null => {
181+ try {
182+ return decodeCatalogSnapshotV1(snapshot);
183+ } catch {
184+ return null;
185+ }
186+};
187+
188+export 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+
208+export 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
1919 import * as Cause from "effect/Cause";
2020 import * as Exit from "effect/Exit";
2121 import * as Schema from "effect/Schema";
22−import type { RuntimeLocalWorkspaceState } from "../local/runtime-context";
22+import type { RuntimeLocalWorkspaceState } from "../workspace/runtime-context";
2323 import {
2424 type LocalStorageServices,
2525 type InstallationStoreShape,
@@ -27,8 +27,8 @@ import { · 1 unmodified line
2727 type WorkspaceConfigStoreShape,
2828 type WorkspaceStateStoreShape,
2929 makeLocalStorageLayer,
30−} from "../local/storage";
31−import { provideOptionalRuntimeLocalWorkspace } from "../local/runtime-context";
30+} from "../workspace/storage";
31+import { provideOptionalRuntimeLocalWorkspace } from "../workspace/runtime-context";
3232 import { runtimeEffectError } from "../effect-errors";
3333
3434 /** 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
5454 getRuntimeLocalWorkspaceOption,
5555 provideOptionalRuntimeLocalWorkspace,
5656 type RuntimeLocalWorkspaceState,
57−} from "../local/runtime-context";
57+} from "../workspace/runtime-context";
5858 import {
5959 exchangeMcpOAuthAuthorizationCode,
6060 startMcpOAuthAuthorization,
@@ -73,12 +73,13 @@ import { · 12 unmodified lines
7373 sourceBindingStateFromSource,
7474 } from "./source-adapters";
7575 import {
76− createDefaultSecretMaterialDeleter,
76+ type DeleteSecretMaterial,
7777 type ResolveSecretMaterial,
78+ SecretMaterialDeleterService,
7879 SecretMaterialResolverService,
7980 SecretMaterialStorerService,
8081 type StoreSecretMaterial,
81−} from "../local/secret-material-providers";
82+} from "../workspace/secret-material-providers";
8283 import {
8384 removeAuthLeaseAndSecrets,
8485 upsertOauth2AuthorizedUserLeaseFromTokenResponse,
@@ -101,7 +102,7 @@ import { · 16 unmodified lines
101102 type RuntimeSourceStore,
102103 RuntimeSourceStoreService,
103104 } from "./source-store";
104−import type { WorkspaceStorageServices } from "../local/storage";
105+import type { WorkspaceStorageServices } from "../workspace/storage";
105106 import { ControlPlaneStore, type ControlPlaneStoreShape } from "../store";
106107 import { runtimeEffectError } from "../effect-errors";
107108
@@ -994,6 +995,7 @@ const upsertSourceOauthClient = (input: { · 886 unmodified lines
994995 source: Source;
995996 oauthClient: SourceOauthClientInput;
996997 storeSecretMaterial: StoreSecretMaterial;
998+ deleteSecretMaterial: DeleteSecretMaterial;
997999 }): Effect.Effect<ResolvedSourceOauthClient, Error, never> =>
9981000 Effect.gen(function* () {
9991001 const adapter = getSourceAdapterForSource(input.source);
@@ -1052,10 +1054,7 @@ const upsertSourceOauthClient = (input: { · 52 unmodified lines
10521054 || previousClientSecretRef.handle !== clientSecretRef.handle
10531055 )
10541056 ) {
1055− const deleteSecretMaterial = createDefaultSecretMaterialDeleter({
1056− rows: input.rows,
1057− });
1058− yield* deleteSecretMaterial(previousClientSecretRef).pipe(
1057+ yield* input.deleteSecretMaterial(previousClientSecretRef).pipe(
10591058 Effect.either,
10601059 Effect.ignore,
10611060 );
@@ -1308,6 +1307,7 @@ const upsertProviderAuthGrant = (input: { · 246 unmodified lines
13081307 grantedScopes: ReadonlyArray<string>;
13091308 refreshToken: string | null;
13101309 storeSecretMaterial: StoreSecretMaterial;
1310+ deleteSecretMaterial: DeleteSecretMaterial;
13111311 existingGrant?: import("#schema").ProviderAuthGrant | null;
13121312 }): Effect.Effect<import("#schema").ProviderAuthGrant, Error, never> =>
13131313 Effect.gen(function* () {
@@ -1356,10 +1356,7 @@ const upsertProviderAuthGrant = (input: { · 42 unmodified lines
13561356 || existingGrant.refreshToken.handle !== refreshTokenRef.handle
13571357 )
13581358 ) {
1359− const deleteSecretMaterial = createDefaultSecretMaterialDeleter({
1360− rows: input.rows,
1361− });
1362− yield* deleteSecretMaterial(existingGrant.refreshToken).pipe(
1359+ yield* input.deleteSecretMaterial(existingGrant.refreshToken).pipe(
13631360 Effect.either,
13641361 Effect.ignore,
13651362 );
@@ -1747,6 +1744,7 @@ const removeProviderAuthGrantInternal = (input: { · 381 unmodified lines
17471744 sourceStore: RuntimeSourceStore;
17481745 workspaceId: WorkspaceId;
17491746 grantId: ProviderAuthGrant["id"];
1747+ deleteSecretMaterial: DeleteSecretMaterial;
17501748 }): Effect.Effect<boolean, Error, WorkspaceStorageServices> =>
17511749 Effect.gen(function* () {
17521750 const grantOption = yield* input.rows.providerAuthGrants.getById(input.grantId);
@@ -1774,7 +1772,7 @@ const removeProviderAuthGrantInternal = (input: { · 21 unmodified lines
17741772 if (latestSource === null) {
17751773 yield* removeAuthLeaseAndSecrets(input.rows, {
17761774 authArtifactId: artifact.id,
1777− });
1775+ }, input.deleteSecretMaterial);
17781776 yield* input.rows.authArtifacts.removeByWorkspaceSourceAndActor({
17791777 workspaceId: artifact.workspaceId,
17801778 sourceId: artifact.sourceId,
@@ -1800,7 +1798,7 @@ const removeProviderAuthGrantInternal = (input: { · 19 unmodified lines
18001798
18011799 yield* removeProviderAuthGrantSecret(input.rows, {
18021800 grant: grantOption.value,
1803− });
1801+ }, input.deleteSecretMaterial);
18041802 yield* input.rows.providerAuthGrants.removeById(input.grantId);
18051803 return true;
18061804 });
@@ -2093,6 +2091,7 @@ const addExecutorHttpSource = (input: { · 286 unmodified lines
20932091 sourceCatalogSync: RuntimeSourceCatalogSyncShape;
20942092 sourceInput: ExecutorHttpEndpointSourceInput;
20952093 storeSecretMaterial: StoreSecretMaterial;
2094+ deleteSecretMaterial: DeleteSecretMaterial;
20962095 resolveSecretMaterial: ResolveSecretMaterial;
20972096 getLocalServerBaseUrl?: () => string | undefined;
20982097 baseUrl?: string | null;
@@ -2305,6 +2304,7 @@ const addExecutorGoogleDiscoverySource = (input: { · 206 unmodified lines
23052304 sourceCatalogSync: RuntimeSourceCatalogSyncShape;
23062305 sourceInput: Extract<ExecutorAddSourceInput, { kind: "google_discovery" }>;
23072306 storeSecretMaterial: StoreSecretMaterial;
2307+ deleteSecretMaterial: DeleteSecretMaterial;
23082308 resolveSecretMaterial: ResolveSecretMaterial;
23092309 getLocalServerBaseUrl?: () => string | undefined;
23102310 baseUrl?: string | null;
@@ -2491,6 +2491,7 @@ const addExecutorGoogleDiscoverySource = (input: { · 180 unmodified lines
24912491 source: persistedDraft,
24922492 oauthClient: input.sourceInput.oauthClient,
24932493 storeSecretMaterial: input.storeSecretMaterial,
2494+ deleteSecretMaterial: input.deleteSecretMaterial,
24942495 });
24952496 }
24962497
@@ -2820,6 +2821,7 @@ type RuntimeSourceAuthDependencies = { · 323 unmodified lines
28202821 sourceCatalogSync: RuntimeSourceCatalogSyncShape;
28212822 resolveSecretMaterial: ResolveSecretMaterial;
28222823 storeSecretMaterial: StoreSecretMaterial;
2824+ deleteSecretMaterial: DeleteSecretMaterial;
28232825 getLocalServerBaseUrl?: () => string | undefined;
28242826 localWorkspaceState?: RuntimeLocalWorkspaceState;
28252827 };
@@ -2880,6 +2882,7 @@ const createRuntimeSourceConnectionService = ( · 54 unmodified lines
28802882 sourceCatalogSync: input.sourceCatalogSync,
28812883 sourceInput,
28822884 storeSecretMaterial: input.storeSecretMaterial,
2885+ deleteSecretMaterial: input.deleteSecretMaterial,
28832886 resolveSecretMaterial: input.resolveSecretMaterial,
28842887 getLocalServerBaseUrl: input.getLocalServerBaseUrl,
28852888 baseUrl: options?.baseUrl,
@@ -2891,6 +2894,7 @@ const createRuntimeSourceConnectionService = ( · 5 unmodified lines
28912894 sourceCatalogSync: input.sourceCatalogSync,
28922895 sourceInput,
28932896 storeSecretMaterial: input.storeSecretMaterial,
2897+ deleteSecretMaterial: input.deleteSecretMaterial,
28942898 resolveSecretMaterial: input.resolveSecretMaterial,
28952899 getLocalServerBaseUrl: input.getLocalServerBaseUrl,
28962900 baseUrl: options?.baseUrl,
@@ -3014,10 +3018,7 @@ const createRuntimeSourceConnectionService = ( · 117 unmodified lines
30143018 const secretRef = sourceOauthClientSecretRef(oauthClient.value);
30153019 const removed = yield* input.rows.workspaceOauthClients.removeById(oauthClientId);
30163020 if (removed && secretRef) {
3017− const deleteSecretMaterial = createDefaultSecretMaterialDeleter({
3018− rows: input.rows,
3019− });
3020− yield* deleteSecretMaterial(secretRef).pipe(
3021+ yield* input.deleteSecretMaterial(secretRef).pipe(
30213022 Effect.either,
30223023 Effect.ignore,
30233024 );
@@ -3034,6 +3035,7 @@ const createRuntimeSourceConnectionService = ( · 10 unmodified lines
30343035 sourceStore: input.sourceStore,
30353036 workspaceId,
30363037 grantId,
3038+ deleteSecretMaterial: input.deleteSecretMaterial,
30373039 }),
30383040 ),
30393041 };
@@ -3329,6 +3331,7 @@ const createRuntimeSourceOAuthSessionService = ( · 289 unmodified lines
33293331 refreshToken: trimOrNull(exchanged.refresh_token),
33303332 existingGrant,
33313333 storeSecretMaterial: input.storeSecretMaterial,
3334+ deleteSecretMaterial: input.deleteSecretMaterial,
33323335 });
33333336
33343337 const targets = yield* Effect.forEach(
@@ -3526,6 +3529,8 @@ const createRuntimeSourceOAuthSessionService = ( · 191 unmodified lines
35263529 rows: input.rows,
35273530 artifact: authArtifact.value,
35283531 tokenResponse: exchanged,
3532+ storeSecretMaterial: input.storeSecretMaterial,
3533+ deleteSecretMaterial: input.deleteSecretMaterial,
35293534 });
35303535 }
35313536
@@ -3698,6 +3703,7 @@ export const RuntimeSourceAuthServiceLive = (input: { · 166 unmodified lines
36983703 const sourceCatalogSync = yield* RuntimeSourceCatalogSyncService;
36993704 const resolveSecretMaterial = yield* SecretMaterialResolverService;
37003705 const storeSecretMaterial = yield* SecretMaterialStorerService;
3706+ const deleteSecretMaterial = yield* SecretMaterialDeleterService;
37013707 const runtimeLocalWorkspace = yield* getRuntimeLocalWorkspaceOption();
37023708
37033709 return createRuntimeSourceAuthService({
@@ -3707,6 +3713,7 @@ export const RuntimeSourceAuthServiceLive = (input: { · 3 unmodified lines
37073713 sourceCatalogSync,
37083714 resolveSecretMaterial,
37093715 storeSecretMaterial,
3716+ deleteSecretMaterial,
37103717 getLocalServerBaseUrl: input.getLocalServerBaseUrl,
37113718 localWorkspaceState: runtimeLocalWorkspace ?? undefined,
37123719 });
packages/platform/sdk/src/runtime/sources/source-discovery.ts+0−1
@@ -1 +0,0 @@
1−export * from "../../sources/discovery";
packages/platform/sdk/src/runtime/sources/source-inspection.ts+0−1
@@ -1 +0,0 @@
1−export * from "../../sources/inspection";
packages/platform/sdk/src/runtime/sources/source-store.test.ts+2−2
@@ -7,9 +7,9 @@ import { · 6 unmodified lines
77 type Source,
88 } from "#schema";
99
10−import type { LoadedLocalExecutorConfig } from "../local/config";
10+import type { LoadedLocalExecutorConfig } from "../workspace-config";
1111 import { buildLocalSourceRecord } from "./source-store";
12−import type { LocalWorkspaceState } from "../local/workspace-state";
12+import type { LocalWorkspaceState } from "../workspace-state";
1313
1414 const workspaceId = WorkspaceIdSchema.make("ws_source_store");
1515 const 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
77 import * as Effect from "effect/Effect";
88 import * as Layer from "effect/Layer";
99
10+import { SourceTypeDeclarationsRefresherService } from "../catalog/source/type-declarations";
1011 import {
1112 SourceArtifactStore,
1213 type WorkspaceStorageServices,
1314 WorkspaceConfigStore,
1415 WorkspaceStateStore,
15−} from "../local/storage";
16−import { RuntimeLocalWorkspaceService } from "../local/runtime-context";
16+} from "../workspace/storage";
17+import { SecretMaterialDeleterService } from "../workspace/secret-material-providers";
18+import { RuntimeLocalWorkspaceService } from "../workspace/runtime-context";
1719 import { ControlPlaneStore, type ControlPlaneStoreShape } from "../store";
1820 import {
1921 loadRuntimeSourceStoreDeps,
@@ -64,7 +66,11 @@ export const loadSourcesInWorkspace = ( · 44 unmodified lines
6466 options: {
6567 actorAccountId?: AccountId | null;
6668 } = {},
67−): Effect.Effect<readonly Source[], Error, WorkspaceStorageServices> =>
69+): Effect.Effect<
70+ readonly Source[],
71+ Error,
72+ WorkspaceStorageServices | SourceTypeDeclarationsRefresherService
73+> =>
6874 Effect.flatMap(
6975 loadRuntimeSourceStoreDeps(rows, workspaceId),
7076 (deps) => loadSourcesInWorkspaceWithDeps(deps, workspaceId, options),
@@ -79,7 +85,7 @@ export const listLinkedSecretSourcesInWorkspace = ( · 8 unmodified lines
7985 ): Effect.Effect<
8086 Map<string, Array<{ sourceId: string; sourceName: string }>>,
8187 Error,
82− WorkspaceStorageServices
88+ WorkspaceStorageServices | SourceTypeDeclarationsRefresherService
8389 > =>
8490 Effect.flatMap(
8591 loadRuntimeSourceStoreDeps(rows, workspaceId),
@@ -93,7 +99,11 @@ export const loadSourceById = ( · 7 unmodified lines
9399 sourceId: Source["id"];
94100 actorAccountId?: AccountId | null;
95101 },
96−): Effect.Effect<Source, Error, WorkspaceStorageServices> =>
102+): Effect.Effect<
103+ Source,
104+ Error,
105+ WorkspaceStorageServices | SourceTypeDeclarationsRefresherService
106+> =>
97107 Effect.flatMap(
98108 loadRuntimeSourceStoreDeps(rows, input.workspaceId),
99109 (deps) => loadSourceByIdWithDeps(deps, input),
@@ -105,11 +115,18 @@ export const removeSourceById = ( · 5 unmodified lines
105115 workspaceId: WorkspaceId;
106116 sourceId: Source["id"];
107117 },
108−): Effect.Effect<boolean, Error, WorkspaceStorageServices> =>
109− Effect.flatMap(
110− loadRuntimeSourceStoreDeps(rows, input.workspaceId),
111− (deps) => removeSourceByIdWithDeps(deps, input),
112− );
118+): Effect.Effect<
119+ boolean,
120+ Error,
121+ | WorkspaceStorageServices
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+ });
113130
114131 export const persistSource = (
115132 rows: ControlPlaneStoreShape,
@@ -117,11 +134,18 @@ export const persistSource = ( · 1 unmodified line
117134 options: {
118135 actorAccountId?: AccountId | null;
119136 } = {},
120−): Effect.Effect<Source, Error, WorkspaceStorageServices> =>
121− Effect.flatMap(
122− loadRuntimeSourceStoreDeps(rows, source.workspaceId),
123− (deps) => persistSourceWithDeps(deps, source, options),
124− );
137+): Effect.Effect<
138+ Source,
139+ Error,
140+ | WorkspaceStorageServices
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+ });
125149
126150 export class RuntimeSourceStoreService extends Context.Tag(
127151 "#runtime/RuntimeSourceStoreService",
@@ -135,6 +159,9 @@ export const RuntimeSourceStoreLive = Layer.effect( · 7 unmodified lines
135159 const workspaceConfigStore = yield* WorkspaceConfigStore;
136160 const workspaceStateStore = yield* WorkspaceStateStore;
137161 const sourceArtifactStore = yield* SourceArtifactStore;
162+ const sourceTypeDeclarationsRefresher =
163+ yield* SourceTypeDeclarationsRefresherService;
164+ const deleteSecretMaterial = yield* SecretMaterialDeleterService;
138165
139166 const deps: RuntimeSourceStoreDeps = {
140167 rows,
@@ -142,6 +169,7 @@ export const RuntimeSourceStoreLive = Layer.effect( · 1 unmodified line
142169 workspaceConfigStore,
143170 workspaceStateStore,
144171 sourceArtifactStore,
172+ sourceTypeDeclarationsRefresher,
145173 };
146174
147175 return RuntimeSourceStoreService.of({
@@ -152,9 +180,9 @@ export const RuntimeSourceStoreLive = Layer.effect( · 4 unmodified lines
152180 loadSourceById: (input) =>
153181 loadSourceByIdWithDeps(deps, input),
154182 removeSourceById: (input) =>
155− removeSourceByIdWithDeps(deps, input),
183+ removeSourceByIdWithDeps(deps, input, deleteSecretMaterial),
156184 persistSource: (source, options = {}) =>
157− persistSourceWithDeps(deps, source, options),
185+ persistSourceWithDeps(deps, source, options, deleteSecretMaterial),
158186 });
159187 }),
160188 );
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
1111
1212 import { authArtifactSecretMaterialRefs } from "../../auth/auth-artifacts";
1313 import { removeAuthLeaseAndSecrets } from "../../auth/auth-leases";
14−import { createDefaultSecretMaterialDeleter } from "../../local/secret-material-providers";
14+import type { DeleteSecretMaterial } from "../../workspace/secret-material-providers";
1515 import type { ControlPlaneStoreShape } from "../../store";
1616
1717 const secretRefKey = (ref: { providerId: string; handle: string }): string =>
@@ -23,13 +23,12 @@ export const cleanupAuthArtifactSecretRefs = ( · 5 unmodified lines
2323 previous: AuthArtifact | null;
2424 next: AuthArtifact | null;
2525 },
26+ deleteSecretMaterial: DeleteSecretMaterial,
2627 ) =>
2728 Effect.gen(function* () {
2829 if (input.previous === null) {
2930 return;
3031 }
31−
32− const deleteSecretMaterial = createDefaultSecretMaterialDeleter({ rows });
3332 const nextRefKeys = new Set(
3433 (input.next === null ? [] : authArtifactSecretMaterialRefs(input.next)).map(
3534 secretRefKey,
@@ -97,6 +96,7 @@ export const removeAuthArtifactsForSource = ( · 61 unmodified lines
9796 workspaceId: WorkspaceId;
9897 sourceId: Source["id"];
9998 },
99+ deleteSecretMaterial: DeleteSecretMaterial,
100100 ) =>
101101 Effect.gen(function* () {
102102 const existingAuthArtifacts = yield* rows.authArtifacts.listByWorkspaceAndSourceId({
@@ -114,7 +114,7 @@ export const removeAuthArtifactsForSource = ( · 11 unmodified lines
114114 (artifact) =>
115115 removeAuthLeaseAndSecrets(rows, {
116116 authArtifactId: artifact.id,
117− }),
117+ }, deleteSecretMaterial),
118118 { discard: true },
119119 );
120120
@@ -124,7 +124,7 @@ export const removeAuthArtifactsForSource = ( · 3 unmodified lines
124124 cleanupAuthArtifactSecretRefs(rows, {
125125 previous: artifact,
126126 next: null,
127− }),
127+ }, deleteSecretMaterial),
128128 { discard: true },
129129 );
130130
packages/platform/sdk/src/runtime/sources/source-store/config.ts+3−3
@@ -6,12 +6,12 @@ import type { · 5 unmodified lines
66 } from "#schema";
77 import { SourceIdSchema } from "#schema";
88
9−import type { LoadedLocalExecutorConfig } from "../../local/config";
10−import { LocalUnsupportedSourceKindError } from "../../local/errors";
9+import type { LoadedLocalExecutorConfig } from "../../workspace-config";
10+import { LocalUnsupportedSourceKindError } from "../../workspace-errors";
1111 import {
1212 fromConfigSecretProviderId,
1313 toConfigSecretProviderId,
14−} from "../../local/config-secrets";
14+} from "../../workspace/config-secrets";
1515 import { getSourceAdapterForSource } from "../source-adapters";
1616 import { slugify } from "../slug";
1717
packages/platform/sdk/src/runtime/sources/source-store/deps.ts+20−27
@@ -1,33 +1,31 @@
11 import type { AccountId, WorkspaceId } from "#schema";
22 import * as Effect from "effect/Effect";
33
4−import type {
5− LoadedLocalExecutorConfig,
6− ResolvedLocalWorkspaceContext,
7−} from "../../local/config";
4+import type { LoadedLocalExecutorConfig } from "../../workspace-config";
5+import {
6+ SourceTypeDeclarationsRefresherService,
7+ type SourceTypeDeclarationsRefresherShape,
8+} from "../../catalog/source/type-declarations";
89 import {
9− LocalExecutorConfigDecodeError,
10− LocalFileSystemError,
11− LocalWorkspaceStateDecodeError,
1210 RuntimeLocalWorkspaceMismatchError,
1311 RuntimeLocalWorkspaceUnavailableError,
14−} from "../../local/errors";
12+} from "../../workspace-errors";
1513 import {
1614 requireRuntimeLocalWorkspace,
1715 type RuntimeLocalWorkspaceState,
18−} from "../../local/runtime-context";
16+} from "../../workspace/runtime-context";
1917 import type {
2018 SourceArtifactStoreShape,
2119 WorkspaceStorageServices,
2220 WorkspaceConfigStoreShape,
2321 WorkspaceStateStoreShape,
24−} from "../../local/storage";
22+} from "../../workspace/storage";
2523 import {
2624 SourceArtifactStore,
2725 WorkspaceConfigStore,
2826 WorkspaceStateStore,
29−} from "../../local/storage";
30−import type { LocalWorkspaceState } from "../../local/workspace-state";
27+} from "../../workspace/storage";
28+import type { LocalWorkspaceState } from "../../workspace-state";
3129 import type { ControlPlaneStoreShape } from "../../store";
3230
3331 export type RuntimeSourceStoreDeps = {
@@ -36,10 +34,10 @@ export type RuntimeSourceStoreDeps = { · 2 unmodified lines
3634 workspaceConfigStore: WorkspaceConfigStoreShape;
3735 workspaceStateStore: WorkspaceStateStoreShape;
3836 sourceArtifactStore: SourceArtifactStoreShape;
37+ sourceTypeDeclarationsRefresher: SourceTypeDeclarationsRefresherShape;
3938 };
4039
4140 export type ResolvedSourceStoreWorkspace = {
42− context: ResolvedLocalWorkspaceContext;
4341 installation: {
4442 workspaceId: WorkspaceId;
4543 accountId: AccountId;
@@ -51,6 +49,9 @@ export type ResolvedSourceStoreWorkspace = { · 5 unmodified lines
5149 workspaceState: LocalWorkspaceState;
5250 };
5351
52+export type RuntimeSourceStoreServices =
53+ WorkspaceStorageServices | SourceTypeDeclarationsRefresherService;
54+
5455 export const resolveRuntimeLocalWorkspaceFromDeps = (
5556 deps: RuntimeSourceStoreDeps,
5657 workspaceId: WorkspaceId,
@@ -58,9 +59,6 @@ export const resolveRuntimeLocalWorkspaceFromDeps = ( · 1 unmodified line
5859 ResolvedSourceStoreWorkspace,
5960 | RuntimeLocalWorkspaceUnavailableError
6061 | RuntimeLocalWorkspaceMismatchError
61− | LocalFileSystemError
62− | LocalExecutorConfigDecodeError
63− | LocalWorkspaceStateDecodeError
6462 | Error,
6563 never
6664 > =>
@@ -73,15 +71,10 @@ export const resolveRuntimeLocalWorkspaceFromDeps = ( · 6 unmodified lines
7371 });
7472 }
7573
76− const loadedConfig = yield* deps.workspaceConfigStore.load(
77− deps.runtimeLocalWorkspace.context,
78− );
79− const workspaceState = yield* deps.workspaceStateStore.load(
80− deps.runtimeLocalWorkspace.context,
81− );
74+ const loadedConfig = yield* deps.workspaceConfigStore.load();
75+ const workspaceState = yield* deps.workspaceStateStore.load();
8276
8377 return {
84− context: deps.runtimeLocalWorkspace.context,
8578 installation: deps.runtimeLocalWorkspace.installation,
8679 workspaceConfigStore: deps.workspaceConfigStore,
8780 workspaceStateStore: deps.workspaceStateStore,
@@ -98,17 +91,16 @@ export const loadRuntimeSourceStoreDeps = ( · 10 unmodified lines
9891 RuntimeSourceStoreDeps,
9992 | RuntimeLocalWorkspaceUnavailableError
10093 | RuntimeLocalWorkspaceMismatchError
101− | LocalFileSystemError
102− | LocalExecutorConfigDecodeError
103− | LocalWorkspaceStateDecodeError
10494 | Error,
105− WorkspaceStorageServices
95+ RuntimeSourceStoreServices
10696 > =>
10797 Effect.gen(function* () {
10898 const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(workspaceId);
10999 const workspaceConfigStore = yield* WorkspaceConfigStore;
110100 const workspaceStateStore = yield* WorkspaceStateStore;
111101 const sourceArtifactStore = yield* SourceArtifactStore;
102+ const sourceTypeDeclarationsRefresher =
103+ yield* SourceTypeDeclarationsRefresherService;
112104
113105 return {
114106 rows,
@@ -116,5 +108,6 @@ export const loadRuntimeSourceStoreDeps = ( · 1 unmodified line
116108 workspaceConfigStore,
117109 workspaceStateStore,
118110 sourceArtifactStore,
111+ sourceTypeDeclarationsRefresher,
119112 };
120113 });
packages/platform/sdk/src/runtime/sources/source-store/lifecycle.ts+11−13
@@ -6,7 +6,8 @@ import { · 5 unmodified lines
66 clearProviderGrantOrphanedAt,
77 markProviderGrantOrphanedIfUnused,
88 } from "../../auth/provider-grant-lifecycle";
9−import type { LocalWorkspaceState } from "../../local/workspace-state";
9+import type { DeleteSecretMaterial } from "../../workspace/secret-material-providers";
10+import type { LocalWorkspaceState } from "../../workspace-state";
1011 import {
1112 stableSourceCatalogId,
1213 stableSourceCatalogRevisionId,
@@ -39,6 +40,7 @@ export const removeSourceByIdWithDeps = ( · 26 unmodified lines
3940 workspaceId: WorkspaceId;
4041 sourceId: Source["id"];
4142 },
43+ deleteSecretMaterial: DeleteSecretMaterial,
4244 ): Effect.Effect<boolean, Error, never> =>
4345 Effect.gen(function* () {
4446 const localWorkspace = yield* resolveRuntimeLocalWorkspaceFromDeps(
@@ -55,7 +57,6 @@ export const removeSourceByIdWithDeps = ( · 10 unmodified lines
5557 };
5658 delete sources[input.sourceId];
5759 yield* localWorkspace.workspaceConfigStore.writeProject({
58− context: localWorkspace.context,
5960 config: {
6061 ...projectConfig,
6162 sources,
@@ -69,11 +70,9 @@ export const removeSourceByIdWithDeps = ( · 7 unmodified lines
6970 sources: remainingSources,
7071 };
7172 yield* localWorkspace.workspaceStateStore.write({
72− context: localWorkspace.context,
7373 state: workspaceState,
7474 });
7575 yield* localWorkspace.sourceArtifactStore.remove({
76− context: localWorkspace.context,
7776 sourceId: input.sourceId,
7877 });
7978 const existingAuthArtifacts =
@@ -91,7 +90,7 @@ export const removeSourceByIdWithDeps = ( · 11 unmodified lines
9190 workspaceId: input.workspaceId,
9291 sourceId: input.sourceId,
9392 });
94− yield* removeAuthArtifactsForSource(deps.rows, input);
93+ yield* removeAuthArtifactsForSource(deps.rows, input, deleteSecretMaterial);
9594 yield* Effect.forEach(
9695 [...removedGrantIds],
9796 (grantId) =>
@@ -112,6 +111,7 @@ export const persistSourceWithDeps = ( · 14 unmodified lines
112111 options: {
113112 actorAccountId?: AccountId | null;
114113 } = {},
114+ deleteSecretMaterial: DeleteSecretMaterial,
115115 ): Effect.Effect<Source, Error, never> =>
116116 Effect.gen(function* () {
117117 const localWorkspace = yield* resolveRuntimeLocalWorkspaceFromDeps(
@@ -155,7 +155,6 @@ export const persistSourceWithDeps = ( · 37 unmodified lines
155155 config: localWorkspace.loadedConfig.config,
156156 });
157157 yield* localWorkspace.workspaceConfigStore.writeProject({
158− context: localWorkspace.context,
159158 config: {
160159 ...projectConfig,
161160 sources,
@@ -175,7 +174,7 @@ export const persistSourceWithDeps = ( · 13 unmodified lines
175174 if (existingRuntimeAuthArtifact !== null) {
176175 yield* removeAuthLeaseAndSecrets(deps.rows, {
177176 authArtifactId: existingRuntimeAuthArtifact.id,
178− });
177+ }, deleteSecretMaterial);
179178 }
180179 yield* deps.rows.authArtifacts.removeByWorkspaceSourceAndActor({
181180 workspaceId: nextSource.workspaceId,
@@ -191,20 +190,20 @@ export const persistSourceWithDeps = ( · 9 unmodified lines
191190 ) {
192191 yield* removeAuthLeaseAndSecrets(deps.rows, {
193192 authArtifactId: existingRuntimeAuthArtifact.id,
194− });
193+ }, deleteSecretMaterial);
195194 }
196195 }
197196
198197 yield* cleanupAuthArtifactSecretRefs(deps.rows, {
199198 previous: existingRuntimeAuthArtifact ?? null,
200199 next: runtimeAuthArtifact,
201− });
200+ }, deleteSecretMaterial);
202201
203202 if (importAuthArtifact === null) {
204203 if (existingImportAuthArtifact !== null) {
205204 yield* removeAuthLeaseAndSecrets(deps.rows, {
206205 authArtifactId: existingImportAuthArtifact.id,
207− });
206+ }, deleteSecretMaterial);
208207 }
209208 yield* deps.rows.authArtifacts.removeByWorkspaceSourceAndActor({
210209 workspaceId: nextSource.workspaceId,
@@ -220,14 +219,14 @@ export const persistSourceWithDeps = ( · 9 unmodified lines
220219 ) {
221220 yield* removeAuthLeaseAndSecrets(deps.rows, {
222221 authArtifactId: existingImportAuthArtifact.id,
223− });
222+ }, deleteSecretMaterial);
224223 }
225224 }
226225
227226 yield* cleanupAuthArtifactSecretRefs(deps.rows, {
228227 previous: existingImportAuthArtifact ?? null,
229228 next: importAuthArtifact,
230− });
229+ }, deleteSecretMaterial);
231230
232231 const previousGrantIds = providerGrantIdsFromArtifacts([
233232 existingRuntimeAuthArtifact,
@@ -271,7 +270,6 @@ export const persistSourceWithDeps = ( · 37 unmodified lines
271270 },
272271 };
273272 yield* localWorkspace.workspaceStateStore.write({
274− context: localWorkspace.context,
275273 state: workspaceState,
276274 });
277275
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
1010
1111 import { sourceAuthFromAuthArtifact } from "../../auth/auth-artifacts";
1212 import { authArtifactSecretMaterialRefs } from "../../auth/auth-artifacts";
13−import { refreshWorkspaceSourceTypeDeclarationsInBackground } from "../../catalog/source/type-declarations";
14−import type { LoadedLocalExecutorConfig } from "../../local/config";
13+import type { LoadedLocalExecutorConfig } from "../../workspace-config";
1514 import {
1615 LocalConfiguredSourceNotFoundError,
17− LocalExecutorConfigDecodeError,
18− LocalFileSystemError,
19− LocalWorkspaceStateDecodeError,
2016 RuntimeLocalWorkspaceMismatchError,
2117 RuntimeLocalWorkspaceUnavailableError,
22−} from "../../local/errors";
23−import type { LocalWorkspaceState } from "../../local/workspace-state";
18+} from "../../workspace-errors";
19+import type { LocalWorkspaceState } from "../../workspace-state";
2420 import { getSourceAdapter } from "../source-adapters";
2521 import {
2622 resolveRuntimeLocalWorkspaceFromDeps,
@@ -131,9 +127,6 @@ export const loadSourcesInWorkspaceWithDeps = ( · 104 unmodified lines
131127 readonly Source[],
132128 | RuntimeLocalWorkspaceUnavailableError
133129 | RuntimeLocalWorkspaceMismatchError
134− | LocalFileSystemError
135− | LocalExecutorConfigDecodeError
136− | LocalWorkspaceStateDecodeError
137130 | LocalConfiguredSourceNotFoundError
138131 | Error,
139132 never
@@ -191,7 +184,6 @@ export const syncWorkspaceSourceTypeDeclarationsWithDeps = ( · 51 unmodified lines
191184 const entries = yield* Effect.forEach(sources, (source) =>
192185 Effect.map(
193186 deps.sourceArtifactStore.read({
194− context: localWorkspace.context,
195187 sourceId: source.id,
196188 }),
197189 (artifact) =>
@@ -204,13 +196,10 @@ export const syncWorkspaceSourceTypeDeclarationsWithDeps = ( · 6 unmodified lines
204196 ),
205197 );
206198
207− yield* Effect.sync(() => {
208− refreshWorkspaceSourceTypeDeclarationsInBackground({
209− context: localWorkspace.context,
210− entries: entries.filter(
211− (entry): entry is NonNullable<typeof entry> => entry !== null,
212− ),
213− });
199+ yield* deps.sourceTypeDeclarationsRefresher.refreshWorkspaceInBackground({
200+ entries: entries.filter(
201+ (entry): entry is NonNullable<typeof entry> => entry !== null,
202+ ),
214203 });
215204 }).pipe(
216205 Effect.withSpan("source.types.refresh_workspace.schedule", {
@@ -236,9 +225,6 @@ export const listLinkedSecretSourcesInWorkspaceWithDeps = ( · 19 unmodified lines
236225 Map<string, Array<{ sourceId: string; sourceName: string }>>,
237226 | RuntimeLocalWorkspaceUnavailableError
238227 | RuntimeLocalWorkspaceMismatchError
239− | LocalFileSystemError
240− | LocalExecutorConfigDecodeError
241− | LocalWorkspaceStateDecodeError
242228 | LocalConfiguredSourceNotFoundError
243229 | Error,
244230 never
@@ -295,9 +281,6 @@ export const loadSourceByIdWithDeps = ( · 50 unmodified lines
295281 Source,
296282 | RuntimeLocalWorkspaceUnavailableError
297283 | RuntimeLocalWorkspaceMismatchError
298− | LocalFileSystemError
299− | LocalExecutorConfigDecodeError
300− | LocalWorkspaceStateDecodeError
301284 | LocalConfiguredSourceNotFoundError
302285 | Error,
303286 never
packages/platform/sdk/src/runtime/workspace-config.ts+9−0
@@ -0,0 +1,9 @@
1+import type { LocalExecutorConfig } from "#schema";
2+
3+export type LoadedExecutorConfig = {
4+ config: LocalExecutorConfig | null;
5+ homeConfig: LocalExecutorConfig | null;
6+ projectConfig: LocalExecutorConfig | null;
7+};
8+
9+export type LoadedLocalExecutorConfig = LoadedExecutorConfig;
packages/platform/sdk/src/runtime/workspace-errors.ts+36−0
@@ -0,0 +1,36 @@
1+import * as Data from "effect/Data";
2+
3+export class RuntimeLocalWorkspaceUnavailableError extends Data.TaggedError(
4+ "RuntimeLocalWorkspaceUnavailableError",
5+)<{
6+ readonly message: string;
7+}> {}
8+
9+export class RuntimeLocalWorkspaceMismatchError extends Data.TaggedError(
10+ "RuntimeLocalWorkspaceMismatchError",
11+)<{
12+ readonly message: string;
13+ readonly requestedWorkspaceId: string;
14+ readonly activeWorkspaceId: string;
15+}> {}
16+
17+export class LocalConfiguredSourceNotFoundError extends Data.TaggedError(
18+ "LocalConfiguredSourceNotFoundError",
19+)<{
20+ readonly message: string;
21+ readonly sourceId: string;
22+}> {}
23+
24+export class LocalSourceArtifactMissingError extends Data.TaggedError(
25+ "LocalSourceArtifactMissingError",
26+)<{
27+ readonly message: string;
28+ readonly sourceId: string;
29+}> {}
30+
31+export 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 @@
1+import {
2+ PolicyIdSchema,
3+ SourceStatusSchema,
4+ TimestampMsSchema,
5+} from "#schema";
6+import * as Schema from "effect/Schema";
7+
8+const 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+
16+const LocalWorkspacePolicyStateSchema = Schema.Struct({
17+ id: PolicyIdSchema,
18+ createdAt: TimestampMsSchema,
19+ updatedAt: TimestampMsSchema,
20+});
21+
22+export 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+
34+export type LocalWorkspaceSourceState = typeof LocalWorkspaceSourceStateSchema.Type;
35+export type LocalWorkspacePolicyState = typeof LocalWorkspacePolicyStateSchema.Type;
36+export type LocalWorkspaceState = typeof LocalWorkspaceStateSchema.Type;
37+
38+export 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
44 import * as Option from "effect/Option";
55
66 import type { AccountId, WorkspaceId } from "#schema";
7−import type {
8− LoadedLocalExecutorConfig,
9− ResolvedLocalWorkspaceContext,
10−} from "./config";
7+import type { ExecutorWorkspaceContext } from "../../workspace";
8+import type { LoadedLocalExecutorConfig } from "../workspace-config";
119 import {
1210 RuntimeLocalWorkspaceMismatchError,
1311 RuntimeLocalWorkspaceUnavailableError,
14−} from "./errors";
12+} from "../workspace-errors";
1513
1614 export type RuntimeLocalWorkspaceState = {
17− context: ResolvedLocalWorkspaceContext;
15+ workspace: ExecutorWorkspaceContext;
1816 installation: {
1917 workspaceId: WorkspaceId;
2018 accountId: AccountId;
packages/platform/sdk/src/runtime/workspace/secret-material-providers.ts+63−0
@@ -0,0 +1,63 @@
1+import type { InstanceConfig } from "../../local/contracts";
2+import * as Context from "effect/Context";
3+import * as Effect from "effect/Effect";
4+
5+import type {
6+ SecretMaterialPurpose,
7+ SecretRef,
8+} from "#schema";
9+
10+export type SecretMaterialResolveContext = {
11+ params?: Readonly<Record<string, string | undefined>>;
12+};
13+
14+export type ResolveSecretMaterial = (input: {
15+ ref: SecretRef;
16+ context?: SecretMaterialResolveContext;
17+}) => Effect.Effect<string, Error, never>;
18+
19+export type StoreSecretMaterial = (input: {
20+ purpose: SecretMaterialPurpose;
21+ value: string;
22+ name?: string | null;
23+ providerId?: string;
24+}) => Effect.Effect<SecretRef, Error, never>;
25+
26+export type DeleteSecretMaterial = (
27+ ref: SecretRef,
28+) => Effect.Effect<boolean, Error, never>;
29+
30+export 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+
43+export type ResolveInstanceConfig = () => Effect.Effect<InstanceConfig, Error, never>;
44+
45+export class SecretMaterialResolverService extends Context.Tag(
46+ "#runtime/SecretMaterialResolverService",
47+)<SecretMaterialResolverService, ResolveSecretMaterial>() {}
48+
49+export class SecretMaterialStorerService extends Context.Tag(
50+ "#runtime/SecretMaterialStorerService",
51+)<SecretMaterialStorerService, StoreSecretMaterial>() {}
52+
53+export class SecretMaterialDeleterService extends Context.Tag(
54+ "#runtime/SecretMaterialDeleterService",
55+)<SecretMaterialDeleterService, DeleteSecretMaterial>() {}
56+
57+export class SecretMaterialUpdaterService extends Context.Tag(
58+ "#runtime/SecretMaterialUpdaterService",
59+)<SecretMaterialUpdaterService, UpdateSecretMaterial>() {}
60+
61+export 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 @@
1+import type { LocalInstallation, LocalExecutorConfig } from "#schema";
2+import * as Context from "effect/Context";
3+import * as Layer from "effect/Layer";
4+
5+import type { LoadedLocalExecutorConfig } from "../workspace-config";
6+import type { LocalSourceArtifact } from "../source-artifacts";
7+import type { LocalWorkspaceState } from "../workspace-state";
8+import type { SourceCatalogSyncResult } from "@executor/source-core";
9+import type { Source } from "#schema";
10+
11+export type InstallationStoreShape = {
12+ load: () => import("effect/Effect").Effect<LocalInstallation, Error, never>;
13+ getOrProvision: () => import("effect/Effect").Effect<LocalInstallation, Error, never>;
14+};
15+
16+export class InstallationStore extends Context.Tag(
17+ "#runtime/InstallationStore",
18+)<InstallationStore, InstallationStoreShape>() {}
19+
20+export 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+
28+export class WorkspaceConfigStore extends Context.Tag(
29+ "#runtime/WorkspaceConfigStore",
30+)<WorkspaceConfigStore, WorkspaceConfigStoreShape>() {}
31+
32+export 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+
39+export class WorkspaceStateStore extends Context.Tag(
40+ "#runtime/WorkspaceStateStore",
41+)<WorkspaceStateStore, WorkspaceStateStoreShape>() {}
42+
43+export 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+
60+export class SourceArtifactStore extends Context.Tag(
61+ "#runtime/SourceArtifactStore",
62+)<SourceArtifactStore, SourceArtifactStoreShape>() {}
63+
64+export type LocalStorageServices =
65+ | InstallationStore
66+ | WorkspaceConfigStore
67+ | WorkspaceStateStore
68+ | SourceArtifactStore;
69+
70+export type WorkspaceStorageServices =
71+ | WorkspaceConfigStore
72+ | WorkspaceStateStore
73+ | SourceArtifactStore;
74+
75+export 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+
86+export 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 @@
1−import { FileSystem } from "@effect/platform";
21 import * as Effect from "effect/Effect";
32
4−import {
5− type LoadedLocalExecutorConfig,
6− type ResolvedLocalWorkspaceContext,
7−} from "./config";
8−import {
9− loadLocalWorkspaceState,
10− writeLocalWorkspaceState,
11− type LocalWorkspaceState,
12−} from "./workspace-state";
3+import { type LoadedLocalExecutorConfig } from "../workspace-config";
4+import { WorkspaceStateStore } from "./storage";
5+import { type LocalWorkspaceState } from "../workspace-state";
136
147 const trimOrNull = (value: string | null | undefined): string | null => {
158 if (value == null) {
@@ -41,11 +34,11 @@ export const derivePolicyConfigKey = ( · 25 unmodified lines
4134 };
4235
4336 const pruneLocalWorkspaceState = (input: {
44− context: ResolvedLocalWorkspaceContext;
4537 loadedConfig: LoadedLocalExecutorConfig;
46−}): Effect.Effect<LocalWorkspaceState, Error, FileSystem.FileSystem> =>
38+}): Effect.Effect<LocalWorkspaceState, Error, WorkspaceStateStore> =>
4739 Effect.gen(function* () {
48− const currentState = yield* loadLocalWorkspaceState(input.context);
40+ const workspaceStateStore = yield* WorkspaceStateStore;
41+ const currentState = yield* workspaceStateStore.load();
4942
5043 const configuredSourceIds = new Set(
5144 Object.keys(input.loadedConfig.config?.sources ?? {}),
@@ -72,8 +65,7 @@ const pruneLocalWorkspaceState = (input: { · 20 unmodified lines
7265 return currentState;
7366 }
7467
75− yield* writeLocalWorkspaceState({
76− context: input.context,
68+ yield* workspaceStateStore.write({
7769 state: nextState,
7870 });
7971
@@ -81,12 +73,10 @@ const pruneLocalWorkspaceState = (input: { · 1 unmodified line
8173 });
8274
8375 export const synchronizeLocalWorkspaceState = (input: {
84− context: ResolvedLocalWorkspaceContext;
8576 loadedConfig: LoadedLocalExecutorConfig;
86−}): Effect.Effect<LoadedLocalExecutorConfig["config"], Error, FileSystem.FileSystem> =>
77+}): Effect.Effect<LoadedLocalExecutorConfig["config"], Error, WorkspaceStateStore> =>
8778 Effect.gen(function* () {
8879 yield* pruneLocalWorkspaceState({
89− context: input.context,
9080 loadedConfig: input.loadedConfig,
9181 });
9282
packages/platform/sdk/src/sources/inspection.ts+1−1
@@ -16,7 +16,7 @@ import { · 15 unmodified lines
1616 } from "../errors";
1717 import * as Effect from "effect/Effect";
1818
19−import { LocalSourceArtifactMissingError } from "../runtime/local/errors";
19+import { LocalSourceArtifactMissingError } from "../runtime/workspace-errors";
2020 import { operationErrors } from "../runtime/policy/operation-errors";
2121 import {
2222 buildLoadedSourceCatalogToolContract,
packages/platform/sdk/src/workspace.ts+15−0
@@ -0,0 +1,15 @@
1+import type {
2+ AccountId,
3+ WorkspaceId,
4+} from "./schema";
5+
6+export type ExecutorWorkspaceDescriptor = {
7+ workspaceName: string;
8+ workspaceRoot?: string | null;
9+ metadata?: Readonly<Record<string, unknown>>;
10+};
11+
12+export type ExecutorWorkspaceContext = ExecutorWorkspaceDescriptor & {
13+ workspaceId: WorkspaceId;
14+ accountId: AccountId;
15+};