History

Add new linting rules for OpenAPI bridge and documents; update workspace references to scope; refactor workspace-related identifiers to scope in various files

Rhys Sullivan committed 7 months ago

Commit d05aa9d1 parent 22bc077153 files · +4493 −2886Browse files

Unified · Split

153 files
  1. .executor/executor.jsonc +105 −10
  2. .oxlintrc.jsonc +13 −0
  3. .tmp-openapi-helper-probe.ts +30 −0
  4. CLAUDE.md +7 −0
  5. apps/executor/package.json +1 −0
  6. apps/executor/src/cli/dev.ts +3 −3
  7. apps/executor/src/cli/main.ts +15 −11
  8. apps/executor/src/distribution/distribution.test.ts +6 −6
  9. apps/executor/src/server/server.real-ingestion.test.ts +11 −11
  10. apps/executor/src/server/server.test.ts +111 −181
  11. apps/web/src/views/add-source.tsx +2 −2
  12. bun.lock +19 −0
  13. examples/sqlite-sdk-consumer/src/main.ts +2 −2
  14. examples/sqlite-sdk-consumer/src/sqlite-backend.ts +84 −81
  15. packages/clients/react/package.json +1 −0
  16. packages/clients/react/src/index.test.tsx +46 −107
  17. packages/clients/react/src/index.ts +21 −21
  18. packages/dev/effect-test-utils/package.json +22 −0
  19. packages/dev/effect-test-utils/src/index.ts +103 −0
  20. packages/dev/effect-test-utils/tsconfig.json +15 −0
  21. packages/hosts/mcp/src/index.ts +24 −24
  22. packages/hosts/mcp/src/paused-result.test.ts +2 −2
  23. packages/platform/api/src/executions/api.ts +1 −1
  24. packages/platform/api/src/executor-context.ts +3 −3
  25. packages/platform/api/src/oauth/api.ts +1 −1
  26. packages/platform/api/src/oauth/http.ts +1 −1
  27. packages/platform/api/src/policies/api.ts +2 −2
  28. packages/platform/api/src/sources/api.ts +6 −6
  29. packages/platform/api/src/sources/http.ts +6 −6
  30. packages/platform/internal/src/tools/executor-admin.ts +103 −103
  31. packages/platform/sdk-file/src/config.test.ts +1 −1
  32. packages/platform/sdk-file/src/config.ts +5 −5
  33. packages/platform/sdk-file/src/executor-state-store.test.ts +1 −1
  34. packages/platform/sdk-file/src/executor-state-store.ts +95 −95
  35. packages/platform/sdk-file/src/index.ts +8 −8
  36. packages/platform/sdk-file/src/installation.test.ts +4 −4
  37. packages/platform/sdk-file/src/installation.ts +9 −6
  38. packages/platform/sdk-file/src/secret-material-providers.ts +7 −7
  39. packages/platform/sdk-file/src/source-artifacts.test.ts +2 −2
  40. packages/platform/sdk-file/src/source-type-declarations.test.ts +2 −3
  41. packages/platform/sdk-file/src/workspace-state.ts +9 −9
  42. packages/platform/sdk/package.json +1 −0
  43. packages/platform/sdk/src/backend.ts +14 −14
  44. packages/platform/sdk/src/errors.ts +3 −1
  45. packages/platform/sdk/src/executions/contracts.ts +3 −1
  46. packages/platform/sdk/src/executor.test.ts +453 −0
  47. packages/platform/sdk/src/executor.ts +95 −78
  48. packages/platform/sdk/src/local/contracts.ts +3 −1
  49. packages/platform/sdk/src/local/operations.ts +32 −20
  50. packages/platform/sdk/src/local/secrets.ts +18 −10
  51. packages/platform/sdk/src/policies/contracts.ts +10 −8
  52. packages/platform/sdk/src/policies/operations.ts +135 −127
  53. packages/platform/sdk/src/runtime/auth/auth-artifacts.ts +19 −15
  54. packages/platform/sdk/src/runtime/auth/auth-leases.ts +30 −17
  55. packages/platform/sdk/src/runtime/auth/mcp-auth-provider.ts +4 −2
  56. packages/platform/sdk/src/runtime/auth/oauth-loopback.test.ts +11 −3
  57. packages/platform/sdk/src/runtime/auth/oauth-loopback.ts +3 −1
  58. packages/platform/sdk/src/runtime/auth/provider-grant-lifecycle.ts +9 −7
  59. packages/platform/sdk/src/runtime/auth/source-auth-material.test.ts +14 −8
  60. packages/platform/sdk/src/runtime/auth/source-auth-material.ts +20 −13
  61. packages/platform/sdk/src/runtime/catalog/catalog-typescript.ts +9 −3
  62. packages/platform/sdk/src/runtime/catalog/prettier-format.ts +3 −1
  63. packages/platform/sdk/src/runtime/catalog/source/reconcile.ts +24 −15
  64. packages/platform/sdk/src/runtime/catalog/source/runtime.test.ts +12 −5
  65. packages/platform/sdk/src/runtime/catalog/source/runtime.ts +65 −56
  66. packages/platform/sdk/src/runtime/catalog/source/snapshot.test.ts +21 −7
  67. packages/platform/sdk/src/runtime/catalog/source/sync.ts +53 −45
  68. packages/platform/sdk/src/runtime/catalog/source/type-declarations.ts +6 −2
  69. packages/platform/sdk/src/runtime/execution/http.test.ts +39 −19
  70. packages/platform/sdk/src/runtime/execution/ir-execution.ts +21 −8
  71. packages/platform/sdk/src/runtime/execution/live.test.ts +8 −2
  72. packages/platform/sdk/src/runtime/execution/live.ts +6 −2
  73. packages/platform/sdk/src/runtime/execution/mcp-resume.test.ts +65 −27
  74. packages/platform/sdk/src/runtime/execution/runtime-config.test.ts +36 −20
  75. packages/platform/sdk/src/runtime/execution/runtime.ts +16 −5
  76. packages/platform/sdk/src/runtime/execution/scope/authorization.ts +33 −16
  77. packages/platform/sdk/src/runtime/execution/scope/environment.ts +55 −37
  78. packages/platform/sdk/src/runtime/execution/scope/local.ts +12 −0
  79. packages/platform/sdk/src/runtime/execution/scope/source-catalog.ts +45 −38
  80. packages/platform/sdk/src/runtime/execution/scope/tool-invoker.ts +92 −65
  81. packages/platform/sdk/src/runtime/execution/service.ts +35 −30
  82. packages/platform/sdk/src/runtime/execution/state.ts +6 −3
  83. packages/platform/sdk/src/runtime/execution/test-http-client.ts +4 −2
  84. packages/platform/sdk/src/runtime/execution/workspace/local.ts +0 −12
  85. packages/platform/sdk/src/runtime/executor-runtime.test.ts +195 −195
  86. packages/platform/sdk/src/runtime/executor-state-store.ts +46 −46
  87. packages/platform/sdk/src/runtime/index.ts +110 −72
  88. packages/platform/sdk/src/runtime/policy/invocation-policy-engine.test.ts +18 −12
  89. packages/platform/sdk/src/runtime/policy/invocation-policy-engine.ts +7 −7
  90. packages/platform/sdk/src/runtime/scope-config.ts +3 −1
  91. packages/platform/sdk/src/runtime/scope-errors.ts +6 −6
  92. packages/platform/sdk/src/runtime/scope-state.ts +6 −6
  93. packages/platform/sdk/src/runtime/scope/config-secrets.ts +0 −0
  94. packages/platform/sdk/src/runtime/scope/runtime-context.ts +85 −0
  95. packages/platform/sdk/src/runtime/scope/scope-sync.ts +16 −10
  96. packages/platform/sdk/src/runtime/scope/secret-material-providers.ts +3 −1
  97. packages/platform/sdk/src/runtime/scope/storage.ts +43 −30
  98. packages/platform/sdk/src/runtime/source-artifacts.ts +3 −1
  99. packages/platform/sdk/src/runtime/sources/executor-tools.ts +67 −60
  100. packages/platform/sdk/src/runtime/sources/graphql-tools.test.ts +13 −3
  101. packages/platform/sdk/src/runtime/sources/source-adapter-fixture-matrix.test.ts +47 −23
  102. packages/platform/sdk/src/runtime/sources/source-adapters/google-discovery.test.ts +24 −8
  103. packages/platform/sdk/src/runtime/sources/source-adapters/graphql.test.ts +24 −8
  104. packages/platform/sdk/src/runtime/sources/source-adapters/index.ts +6 −2
  105. packages/platform/sdk/src/runtime/sources/source-adapters/internal.ts +3 −1
  106. packages/platform/sdk/src/runtime/sources/source-adapters/mcp.test.ts +73 −34
  107. packages/platform/sdk/src/runtime/sources/source-auth-service.ts +379 −305
  108. packages/platform/sdk/src/runtime/sources/source-credential-interactions.ts +4 −1
  109. packages/platform/sdk/src/runtime/sources/source-definitions.test.ts +20 −14
  110. packages/platform/sdk/src/runtime/sources/source-definitions.ts +20 −14
  111. packages/platform/sdk/src/runtime/sources/source-discovery.test.ts +8 −2
  112. packages/platform/sdk/src/runtime/sources/source-inspection.test.ts +11 −3
  113. packages/platform/sdk/src/runtime/sources/source-store.test.ts +20 −10
  114. packages/platform/sdk/src/runtime/sources/source-store.ts +60 −52
  115. packages/platform/sdk/src/runtime/sources/source-store/auth.ts +27 −18
  116. packages/platform/sdk/src/runtime/sources/source-store/config.ts +16 −6
  117. packages/platform/sdk/src/runtime/sources/source-store/deps.ts +60 −52
  118. packages/platform/sdk/src/runtime/sources/source-store/lifecycle.ts +69 −60
  119. packages/platform/sdk/src/runtime/sources/source-store/records.ts +76 −65
  120. packages/platform/sdk/src/runtime/workspace/runtime-context.ts +0 −78
  121. packages/platform/sdk/src/schema/common.ts +3 −1
  122. packages/platform/sdk/src/schema/enums.ts +4 −4
  123. packages/platform/sdk/src/schema/ids.ts +11 −11
  124. packages/platform/sdk/src/schema/index.ts +1 −1
  125. packages/platform/sdk/src/schema/models/auth-artifact.ts +9 −6
  126. packages/platform/sdk/src/schema/models/auth-lease.ts +9 −6
  127. packages/platform/sdk/src/schema/models/code-migration.ts +6 −2
  128. packages/platform/sdk/src/schema/models/execution.ts +9 −6
  129. packages/platform/sdk/src/schema/models/local-config.ts +6 −2
  130. packages/platform/sdk/src/schema/models/local-installation.ts +4 −4
  131. packages/platform/sdk/src/schema/models/policy.ts +22 −15
  132. packages/platform/sdk/src/schema/models/provider-auth-grant.ts +11 −8
  133. packages/platform/sdk/src/schema/models/scope-oauth-client.ts +12 −8
  134. packages/platform/sdk/src/schema/models/secret-material.ts +9 −3
  135. packages/platform/sdk/src/schema/models/source-auth-session.ts +11 −8
  136. packages/platform/sdk/src/schema/models/source-catalog.ts +7 −3
  137. packages/platform/sdk/src/schema/models/source-inspection.ts +6 −2
  138. packages/platform/sdk/src/schema/models/source-oauth-client.ts +22 −18
  139. packages/platform/sdk/src/schema/models/source.ts +18 −10
  140. packages/platform/sdk/src/schema/schema.test.ts +5 −1
  141. packages/platform/sdk/src/scope.ts +17 −0
  142. packages/platform/sdk/src/sources/contracts.ts +11 −11
  143. packages/platform/sdk/src/sources/discovery.ts +3 −1
  144. packages/platform/sdk/src/sources/inspection.ts +25 −19
  145. packages/platform/sdk/src/sources/operations.ts +43 −34
  146. packages/platform/sdk/src/workspace.ts +0 −15
  147. packages/sources/core/src/source-models.ts +6 −6
  148. packages/sources/google-discovery/src/adapter.test.ts +1 −1
  149. packages/sources/google-discovery/src/adapter.ts +3 −3
  150. tools/oxlint/README.md +2 −0
  151. tools/oxlint/plugin.mjs +4 −0
  152. tools/oxlint/rules/no-manual-httpapi-openapi-bridge.mjs +65 −0
  153. tools/oxlint/rules/no-manual-openapi-documents.mjs +108 −0
.executor/executor.jsonc+105−10
@@ -1,22 +1,117 @@
1{1{
2 "sources": {2 "sources": {
3 "github": {3 "github": {
4 "kind": "openapi",
5 "name": "GitHub",4 "name": "GitHub",
6 "connection": {5 "connection": {
7 "endpoint": "https://api.github.com/"6 "endpoint": "https://api.github.com"
8 },7 },
8 "kind": "openapi",
9 "binding": {9 "binding": {
10 "specUrl": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json",10 "specUrl": "https://example.com/github-openapi.yaml",
11 "defaultHeaders": null11 "defaultHeaders": null
12 }12 }
13 }13 },
14 },14 "googleapis": {
15 "policies": {15 "name": "Drive",
16 "demo.*": {16 "namespace": "google.drive",
17 "match": "demo.*",17 "connection": {
18 "action": "allow",18 "endpoint": "http://127.0.0.1:60144/drive/$discovery/rest?version=v3"
19 "approval": "auto"19 },
20 "kind": "google_discovery",
21 "binding": {
22 "service": "drive",
23 "version": "v3",
24 "discoveryUrl": "http://127.0.0.1:60144/drive/$discovery/rest?version=v3",
25 "defaultHeaders": null,
26 "scopes": [
27 "scope:drive.readonly"
28 ]
29 }
30 },
31 "google-gmail": {
32 "name": "Gmail",
33 "namespace": "google.gmail",
34 "connection": {
35 "endpoint": "http://127.0.0.1:60197/gmail/$discovery/rest?version=v1"
36 },
37 "kind": "google_discovery",
38 "binding": {
39 "service": "gmail",
40 "version": "v1",
41 "discoveryUrl": "http://127.0.0.1:60197/gmail/$discovery/rest?version=v1",
42 "defaultHeaders": null,
43 "scopes": [
44 "scope:gmail.readonly"
45 ]
46 }
47 },
48 "google-calendar": {
49 "name": "Calendar",
50 "namespace": "google.calendar",
51 "connection": {
52 "endpoint": "http://127.0.0.1:60190/calendar/$discovery/rest?version=v3"
53 },
54 "kind": "google_discovery",
55 "binding": {
56 "service": "calendar",
57 "version": "v3",
58 "discoveryUrl": "http://127.0.0.1:60190/calendar/$discovery/rest?version=v3",
59 "defaultHeaders": null,
60 "scopes": [
61 "scope:calendar.readonly"
62 ]
63 }
64 },
65 "google-drive": {
66 "name": "Drive",
67 "namespace": "google.drive",
68 "connection": {
69 "endpoint": "http://127.0.0.1:60205/drive/$discovery/rest?version=v3"
70 },
71 "kind": "google_discovery",
72 "binding": {
73 "service": "drive",
74 "version": "v3",
75 "discoveryUrl": "http://127.0.0.1:60205/drive/$discovery/rest?version=v3",
76 "defaultHeaders": null,
77 "scopes": [
78 "scope:drive.readonly"
79 ]
80 }
81 },
82 "google-drive-2": {
83 "name": "Drive",
84 "namespace": "google.drive",
85 "connection": {
86 "endpoint": "https://example.test/drive/$discovery/rest?version=v3"
87 },
88 "kind": "google_discovery",
89 "binding": {
90 "service": "drive",
91 "version": "v3",
92 "discoveryUrl": "https://example.test/drive/$discovery/rest?version=v3",
93 "defaultHeaders": null,
94 "scopes": [
95 "scope:drive.readonly"
96 ]
97 }
98 },
99 "google-gmail-2": {
100 "kind": "google_discovery",
101 "name": "Gmail",
102 "namespace": "google.gmail",
103 "connection": {
104 "endpoint": "https://example.test/gmail/$discovery/rest?version=v1"
105 },
106 "binding": {
107 "service": "gmail",
108 "version": "v1",
109 "discoveryUrl": "https://example.test/gmail/$discovery/rest?version=v1",
110 "defaultHeaders": null,
111 "scopes": [
112 "scope:gmail.readonly"
113 ]
114 }
20 }115 }
21 }116 }
22}117}
.oxlintrc.jsonc+13−0
@@ -43,6 +43,8 @@ · 42 unmodified lines
43 "executor-monorepo/no-cross-workspace-relative-imports": "error",43 "executor-monorepo/no-cross-workspace-relative-imports": "error",
44 "executor-monorepo/no-direct-effect-tag-read": "error",44 "executor-monorepo/no-direct-effect-tag-read": "error",
45 "executor-monorepo/no-effect-run-in-effect-vitest-tests": "error",45 "executor-monorepo/no-effect-run-in-effect-vitest-tests": "error",
46 "executor-monorepo/no-manual-httpapi-openapi-bridge": "error",
47 "executor-monorepo/no-manual-openapi-documents": "error",
46 "executor-monorepo/no-node-fs-with-effect-imports": "error",48 "executor-monorepo/no-node-fs-with-effect-imports": "error",
47 "executor-monorepo/no-raw-effect-fail-errors": "error",49 "executor-monorepo/no-raw-effect-fail-errors": "error",
48 "executor-monorepo/no-yield-effect-fail": "error",50 "executor-monorepo/no-yield-effect-fail": "error",
@@ -50,6 +52,17 @@ · 1 unmodified line
50 },52 },
51 53
52 "overrides": [54 "overrides": [
55 {
56 // SDK tests still use relative workspace imports to avoid introducing
57 // package-graph cycles from test-only dependencies.
58 "files": [
59 "packages/platform/sdk/src/executor.test.ts",
60 "packages/platform/sdk/src/runtime/executor-runtime.test.ts"
61 ],
62 "rules": {
63 "executor-monorepo/no-cross-workspace-relative-imports": "off"
64 }
65 },
53 {66 {
54 // JSON Schema conditionals legitimately use a `then` key.67 // JSON Schema conditionals legitimately use a `then` key.
55 "files": [68 "files": [
.tmp-openapi-helper-probe.ts+30−0
@@ -0,0 +1,30 @@
1import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from '@effect/platform';
2import { startOpenApiTestServer } from '@executor/effect-test-utils';
3import * as Effect from 'effect/Effect';
4import * as Layer from 'effect/Layer';
5import * as Schema from 'effect/Schema';
6
7const ownerParam = HttpApiSchema.param('owner', Schema.String);
8const repoParam = HttpApiSchema.param('repo', Schema.String);
9class DemoReposApi extends HttpApiGroup.make('repos').add(
10 HttpApiEndpoint.get('getRepo')`/repos/${ownerParam}/${repoParam}`.addSuccess(
11 Schema.Struct({ full_name: Schema.String, private: Schema.Boolean }),
12 ),
13) {}
14class DemoApi extends HttpApi.make('demo').add(DemoReposApi) {}
15const live = HttpApiBuilder.group(DemoApi, 'repos', (handlers) =>
16 handlers.handle('getRepo', ({ path, request }) => {
17 console.log('auth', request.headers.authorization);
18 return Effect.succeed({ full_name: `${path.owner}/${path.repo}`, private: false });
19 }),
20);
21const main = async () => {
22 const server = await startOpenApiTestServer({ apiLayer: HttpApiBuilder.api(DemoApi).pipe(Layer.provide(live)) });
23 console.log('base', server.baseUrl, server.specUrl);
24 const spec = await fetch(server.specUrl);
25 console.log('spec', spec.status, await spec.text());
26 const resp = await fetch(`${server.baseUrl}/repos/vercel/ai`, { headers: { authorization: 'Bearer test' } });
27 console.log('repo', resp.status, await resp.text());
28 await server.close();
29};
30main().catch((e) => { console.error(e); process.exit(1); });
CLAUDE.md+7−0
@@ -0,0 +1,7 @@
1# Repo Agent Notes
2
3- When a test needs an OpenAPI spec, generate it from Effect `HttpApi` with `OpenApi.fromApi(...)`.
4- When a test needs to serve an Effect `HttpApi` plus `/openapi.json`, use `@executor/effect-test-utils` via `startOpenApiTestServer(...)` or `makeOpenApiTestServer(...)`.
5- Do not hand-author inline OpenAPI documents in normal tests.
6- Do not hand-roll `HttpApiBuilder.toWebHandler(...)` to Node HTTP bridges for OpenAPI test servers.
7- Raw OpenAPI parser/import-fidelity tests are the exception; those may keep literal OpenAPI documents where the point of the test is the raw document itself.
apps/executor/package.json+1−0
@@ -45,6 +45,7 @@ · 44 unmodified lines
45 "effect": "catalog:"45 "effect": "catalog:"
46 },46 },
47 "devDependencies": {47 "devDependencies": {
48 "@executor/effect-test-utils": "workspace:*",
48 "@executor/mcp-elicitation-demo": "workspace:*",49 "@executor/mcp-elicitation-demo": "workspace:*",
49 "@executor/runtime-ses": "workspace:*",50 "@executor/runtime-ses": "workspace:*",
50 "@effect/vitest": "catalog:",51 "@effect/vitest": "catalog:",
apps/executor/src/cli/dev.ts+3−3
@@ -1,5 +1,5 @@
1import type { ExecutorApiClient } from "@executor/platform-api";1import type { ExecutorApiClient } from "@executor/platform-api";
2import { WorkspaceIdSchema } from "@executor/platform-sdk/schema";2import { ScopeIdSchema } from "@executor/platform-sdk/schema";
3import * as Effect from "effect/Effect";3import * as Effect from "effect/Effect";
4 4
5const readBindingString = (binding: Record<string, unknown>, key: string): string | null =>5const readBindingString = (binding: Record<string, unknown>, key: string): string | null =>
@@ -41,7 +41,7 @@ export const seedDemoMcpSourceInWorkspace = ( · 35 unmodified lines
41 input: SeedDemoMcpSourceInput,41 input: SeedDemoMcpSourceInput,
42): Effect.Effect<SeedDemoMcpSourceResult, unknown, never> =>42): Effect.Effect<SeedDemoMcpSourceResult, unknown, never> =>
43 Effect.gen(function* () {43 Effect.gen(function* () {
44 const workspaceId = WorkspaceIdSchema.make(input.workspaceId);44 const workspaceId = ScopeIdSchema.make(input.workspaceId);
45 45
46 const existing = yield* input.client.sources.list({46 const existing = yield* input.client.sources.list({
47 path: {47 path: {
@@ -138,7 +138,7 @@ export const seedGithubOpenApiSourceInWorkspace = ( · 90 unmodified lines
138 input: SeedGithubOpenApiSourceInput,138 input: SeedGithubOpenApiSourceInput,
139): Effect.Effect<SeedDemoMcpSourceResult, unknown, never> =>139): Effect.Effect<SeedDemoMcpSourceResult, unknown, never> =>
140 Effect.gen(function* () {140 Effect.gen(function* () {
141 const workspaceId = WorkspaceIdSchema.make(input.workspaceId);141 const workspaceId = ScopeIdSchema.make(input.workspaceId);
142 142
143 const existing = yield* input.client.sources.list({143 const existing = yield* input.client.sources.list({
144 path: {144 path: {
apps/executor/src/cli/main.ts+15−11
@@ -289,8 +289,8 @@ const loadRunWorkflowText = (): Effect.Effect<string, Error, never> => · 288 unmodified lines
289 const environment = yield* Effect.gen(function* () {289 const environment = yield* Effect.gen(function* () {
290 const resolveExecutionEnvironment = yield* RuntimeExecutionResolverService;290 const resolveExecutionEnvironment = yield* RuntimeExecutionResolverService;
291 return yield* resolveExecutionEnvironment({291 return yield* resolveExecutionEnvironment({
292 workspaceId: executor.workspaceId,292 scopeId: executor.scopeId,
293 accountId: executor.accountId,293 actorScopeId: executor.actorScopeId,
294 executionId: ExecutionIdSchema.make("exec_help"),294 executionId: ExecutionIdSchema.make("exec_help"),
295 });295 });
296 }).pipe(296 }).pipe(
@@ -403,7 +403,7 @@ const getLocalAuthedClient = (baseUrl: string = DEFAULT_SERVER_BASE_URL) => · 106 unmodified lines
403 const installation = yield* bootstrapClient.local.installation({});403 const installation = yield* bootstrapClient.local.installation({});
404 const client = yield* createExecutorApiClient({404 const client = yield* createExecutorApiClient({
405 baseUrl,405 baseUrl,
406 accountId: installation.accountId,406 accountId: installation.actorScopeId,
407 });407 });
408 408
409 return {409 return {
@@ -574,6 +574,10 @@ const getServerStatus = ( · 164 unmodified lines
574 const installation = reachable574 const installation = reachable
575 ? yield* getBootstrapClient(baseUrl).pipe(575 ? yield* getBootstrapClient(baseUrl).pipe(
576 Effect.flatMap((client) => client.local.installation({})),576 Effect.flatMap((client) => client.local.installation({})),
577 Effect.map((installation) => ({
578 accountId: installation.actorScopeId,
579 workspaceId: installation.scopeId,
580 })),
577 Effect.catchAll(() => Effect.succeed(null)),581 Effect.catchAll(() => Effect.succeed(null)),
578 )582 )
579 : null;583 : null;
@@ -923,7 +927,7 @@ const promptInteraction = (input: { · 343 unmodified lines
923 927
924const waitForExecutionProgress = (input: {928const waitForExecutionProgress = (input: {
925 client: ExecutorApiClient;929 client: ExecutorApiClient;
926 workspaceId: ExecutionEnvelope["execution"]["workspaceId"];930 workspaceId: ExecutionEnvelope["execution"]["scopeId"];
927 executionId: ExecutionEnvelope["execution"]["id"];931 executionId: ExecutionEnvelope["execution"]["id"];
928 pendingInteractionId: ExecutionInteraction["id"];932 pendingInteractionId: ExecutionInteraction["id"];
929}) =>933}) =>
@@ -987,7 +991,7 @@ const seedDemoMcpSource = (input: { · 57 unmodified lines
987 const { installation, client } = yield* getLocalAuthedClient(input.baseUrl);991 const { installation, client } = yield* getLocalAuthedClient(input.baseUrl);
988 const result = yield* seedDemoMcpSourceInWorkspace({992 const result = yield* seedDemoMcpSourceInWorkspace({
989 client,993 client,
990 workspaceId: installation.workspaceId,994 workspaceId: installation.scopeId,
991 endpoint: input.endpoint,995 endpoint: input.endpoint,
992 name: input.name,996 name: input.name,
993 namespace: input.namespace,997 namespace: input.namespace,
@@ -1011,7 +1015,7 @@ const seedGithubOpenApiSource = (input: { · 17 unmodified lines
1011 const { installation, client } = yield* getLocalAuthedClient(input.baseUrl);1015 const { installation, client } = yield* getLocalAuthedClient(input.baseUrl);
1012 const result = yield* seedGithubOpenApiSourceInWorkspace({1016 const result = yield* seedGithubOpenApiSourceInWorkspace({
1013 client,1017 client,
1014 workspaceId: installation.workspaceId,1018 workspaceId: installation.scopeId,
1015 endpoint: input.endpoint,1019 endpoint: input.endpoint,
1016 specUrl: input.specUrl,1020 specUrl: input.specUrl,
1017 name: input.name,1021 name: input.name,
@@ -1026,7 +1030,7 @@ const seedGithubOpenApiSource = (input: { · 8 unmodified lines
1026 1030
1027const driveExecution = (input: {1031const driveExecution = (input: {
1028 client: ExecutorApiClient;1032 client: ExecutorApiClient;
1029 workspaceId: ExecutionEnvelope["execution"]["workspaceId"];1033 workspaceId: ExecutionEnvelope["execution"]["scopeId"];
1030 envelope: ExecutionEnvelope;1034 envelope: ExecutionEnvelope;
1031 baseUrl: string;1035 baseUrl: string;
1032 shouldOpenUrls: boolean;1036 shouldOpenUrls: boolean;
@@ -1297,7 +1301,7 @@ const callCommand = Command.make( · 264 unmodified lines
1297 const { installation, client } = yield* getLocalAuthedClient(baseUrl);1301 const { installation, client } = yield* getLocalAuthedClient(baseUrl);
1298 const created = yield* client.executions.create({1302 const created = yield* client.executions.create({
1299 path: {1303 path: {
1300 workspaceId: installation.workspaceId,1304 workspaceId: installation.scopeId,
1301 },1305 },
1302 payload: {1306 payload: {
1303 code: resolvedCode,1307 code: resolvedCode,
@@ -1307,7 +1311,7 @@ const callCommand = Command.make( · 3 unmodified lines
1307 1311
1308 const settled = yield* driveExecution({1312 const settled = yield* driveExecution({
1309 client,1313 client,
1310 workspaceId: installation.workspaceId,1314 workspaceId: installation.scopeId,
1311 envelope: created,1315 envelope: created,
1312 baseUrl,1316 baseUrl,
1313 shouldOpenUrls: !noOpen,1317 shouldOpenUrls: !noOpen,
@@ -1333,14 +1337,14 @@ const resumeCommand = Command.make( · 19 unmodified lines
1333 );1337 );
1334 const execution = yield* client.executions.get({1338 const execution = yield* client.executions.get({
1335 path: {1339 path: {
1336 workspaceId: installation.workspaceId,1340 workspaceId: installation.scopeId,
1337 executionId: decodedExecutionId,1341 executionId: decodedExecutionId,
1338 },1342 },
1339 });1343 });
1340 1344
1341 const settled = yield* driveExecution({1345 const settled = yield* driveExecution({
1342 client,1346 client,
1343 workspaceId: installation.workspaceId,1347 workspaceId: installation.scopeId,
1344 envelope: execution,1348 envelope: execution,
1345 baseUrl,1349 baseUrl,
1346 shouldOpenUrls: !noOpen,1350 shouldOpenUrls: !noOpen,
apps/executor/src/distribution/distribution.test.ts+6−6
@@ -65,8 +65,8 @@ describe("distribution flow", () => { · 64 unmodified lines
65 const installationResponse = yield* harness.fetchText("/v1/local/installation");65 const installationResponse = yield* harness.fetchText("/v1/local/installation");
66 expect(installationResponse.status).toBe(200);66 expect(installationResponse.status).toBe(200);
67 const installation = JSON.parse(installationResponse.body) as {67 const installation = JSON.parse(installationResponse.body) as {
68 workspaceId: string;68 scopeId: string;
69 accountId: string;69 actorScopeId: string;
70 };70 };
71 71
72 const sesCall = yield* runCommand(72 const sesCall = yield* runCommand(
@@ -88,12 +88,12 @@ describe("distribution flow", () => { · 15 unmodified lines
88 const installationAfterRestart = JSON.parse(88 const installationAfterRestart = JSON.parse(
89 installationAfterRestartResponse.body,89 installationAfterRestartResponse.body,
90 ) as {90 ) as {
91 workspaceId: string;91 scopeId: string;
92 accountId: string;92 actorScopeId: string;
93 };93 };
94 94
95 expect(installationAfterRestart.workspaceId).toBe(installation.workspaceId);95 expect(installationAfterRestart.scopeId).toBe(installation.scopeId);
96 expect(installationAfterRestart.accountId).toBe(installation.accountId);96 expect(installationAfterRestart.actorScopeId).toBe(installation.actorScopeId);
97 97
98 yield* runCommand(["down", "--base-url", harness.baseUrl]);98 yield* runCommand(["down", "--base-url", harness.baseUrl]);
99 });99 });
apps/executor/src/server/server.real-ingestion.test.ts+11−11
@@ -39,7 +39,7 @@ const createApiClientHarness = () => · 38 unmodified lines
39 const installation = yield* bootstrapClient.local.installation({});39 const installation = yield* bootstrapClient.local.installation({});
40 const client = yield* createExecutorApiClient({40 const client = yield* createExecutorApiClient({
41 baseUrl: server.baseUrl,41 baseUrl: server.baseUrl,
42 accountId: installation.accountId,42 accountId: installation.actorScopeId,
43 });43 });
44 44
45 return {45 return {
@@ -75,7 +75,7 @@ describe("local-executor-server real ingestion", () => { · 29 unmodified lines
75 75
76 const connected = yield* client.sources.connect({76 const connected = yield* client.sources.connect({
77 path: {77 path: {
78 workspaceId: installation.workspaceId,78 workspaceId: installation.scopeId,
79 },79 },
80 payload: {80 payload: {
81 kind: "openapi",81 kind: "openapi",
@@ -96,7 +96,7 @@ describe("local-executor-server real ingestion", () => { · 14 unmodified lines
96 96
97 const inspection = yield* client.sources.inspection({97 const inspection = yield* client.sources.inspection({
98 path: {98 path: {
99 workspaceId: installation.workspaceId,99 workspaceId: installation.scopeId,
100 sourceId: connected.source.id,100 sourceId: connected.source.id,
101 },101 },
102 });102 });
@@ -106,7 +106,7 @@ describe("local-executor-server real ingestion", () => { · 3 unmodified lines
106 106
107 const discoveredTools = yield* client.sources.inspectionDiscover({107 const discoveredTools = yield* client.sources.inspectionDiscover({
108 path: {108 path: {
109 workspaceId: installation.workspaceId,109 workspaceId: installation.scopeId,
110 sourceId: connected.source.id,110 sourceId: connected.source.id,
111 },111 },
112 payload: {112 payload: {
@@ -137,7 +137,7 @@ describe("local-executor-server real ingestion", () => { · 24 unmodified lines
137 137
138 const connected = yield* client.sources.connect({138 const connected = yield* client.sources.connect({
139 path: {139 path: {
140 workspaceId: installation.workspaceId,140 workspaceId: installation.scopeId,
141 },141 },
142 payload: {142 payload: {
143 kind: "graphql",143 kind: "graphql",
@@ -159,7 +159,7 @@ describe("local-executor-server real ingestion", () => { · 15 unmodified lines
159 159
160 const inspection = yield* client.sources.inspection({160 const inspection = yield* client.sources.inspection({
161 path: {161 path: {
162 workspaceId: installation.workspaceId,162 workspaceId: installation.scopeId,
163 sourceId: connected.source.id,163 sourceId: connected.source.id,
164 },164 },
165 });165 });
@@ -175,7 +175,7 @@ describe("local-executor-server real ingestion", () => { · 9 unmodified lines
175 175
176 const toolDetail = yield* client.sources.inspectionTool({176 const toolDetail = yield* client.sources.inspectionTool({
177 path: {177 path: {
178 workspaceId: installation.workspaceId,178 workspaceId: installation.scopeId,
179 sourceId: connected.source.id,179 sourceId: connected.source.id,
180 toolPath: "linear.administrableTeams",180 toolPath: "linear.administrableTeams",
181 },181 },
@@ -191,7 +191,7 @@ describe("local-executor-server real ingestion", () => { · 9 unmodified lines
191 191
192 const storedSource = yield* client.sources.get({192 const storedSource = yield* client.sources.get({
193 path: {193 path: {
194 workspaceId: installation.workspaceId,194 workspaceId: installation.scopeId,
195 sourceId: connected.source.id,195 sourceId: connected.source.id,
196 },196 },
197 });197 });
@@ -218,7 +218,7 @@ describe("local-executor-server real ingestion", () => { · 20 unmodified lines
218 218
219 const connected = yield* client.sources.connect({219 const connected = yield* client.sources.connect({
220 path: {220 path: {
221 workspaceId: installation.workspaceId,221 workspaceId: installation.scopeId,
222 },222 },
223 payload: {223 payload: {
224 kind: "mcp",224 kind: "mcp",
@@ -235,7 +235,7 @@ describe("local-executor-server real ingestion", () => { · 10 unmodified lines
235 235
236 const inspection = yield* client.sources.inspection({236 const inspection = yield* client.sources.inspection({
237 path: {237 path: {
238 workspaceId: installation.workspaceId,238 workspaceId: installation.scopeId,
239 sourceId: connected.source.id,239 sourceId: connected.source.id,
240 },240 },
241 });241 });
@@ -245,7 +245,7 @@ describe("local-executor-server real ingestion", () => { · 3 unmodified lines
245 245
246 const discoveredTools = yield* client.sources.inspectionDiscover({246 const discoveredTools = yield* client.sources.inspectionDiscover({
247 path: {247 path: {
248 workspaceId: installation.workspaceId,248 workspaceId: installation.scopeId,
249 sourceId: connected.source.id,249 sourceId: connected.source.id,
250 },250 },
251 payload: {251 payload: {
apps/executor/src/server/server.test.ts+111−181
@@ -1,4 +1,4 @@
1import { createServer, type IncomingMessage, type ServerResponse } from "node:http";1import { type IncomingMessage, type ServerResponse } from "node:http";
2import { randomUUID } from "node:crypto";2import { randomUUID } from "node:crypto";
3 3
4import {4import {
@@ -6,17 +6,19 @@ import { · 1 unmodified line
6 HttpApiEndpoint,6 HttpApiEndpoint,
7 HttpApiGroup,7 HttpApiGroup,
8 HttpApiSchema,8 HttpApiSchema,
9 OpenApi,9 HttpApiBuilder,
10 FileSystem,10 FileSystem,
11} from "@effect/platform";11} from "@effect/platform";
12import { NodeFileSystem } from "@effect/platform-node";12import { NodeFileSystem } from "@effect/platform-node";
13import { describe, expect, it } from "@effect/vitest";13import { describe, expect, it } from "@effect/vitest";
14import { assertTrue } from "@effect/vitest/utils";14import { assertTrue } from "@effect/vitest/utils";
15import * as Effect from "effect/Effect";15import * as Effect from "effect/Effect";
16import * as Layer from "effect/Layer";
16import * as Schema from "effect/Schema";17import * as Schema from "effect/Schema";
17 18
18import { startMcpElicitationDemoServer } from "@executor/mcp-elicitation-demo";19import { startMcpElicitationDemoServer } from "@executor/mcp-elicitation-demo";
19import { makeToolInvokerFromTools, toTool } from "@executor/codemode-core";20import { makeToolInvokerFromTools, toTool } from "@executor/codemode-core";
21import { startOpenApiTestServer } from "@executor/effect-test-utils";
20import {22import {
21 createExecutorApiClient,23 createExecutorApiClient,
22 executorOpenApiSpec,24 executorOpenApiSpec,
@@ -122,7 +124,7 @@ const writeConfiguredLocalMcpSource = (input: { · 99 unmodified lines
122 124
123 const source = {125 const source = {
124 id: sourceId,126 id: sourceId,
125 workspaceId: installation.workspaceId,127 scopeId: installation.scopeId,
126 name: input.name ?? "Demo",128 name: input.name ?? "Demo",
127 kind: "mcp" as const,129 kind: "mcp" as const,
128 endpoint: input.endpoint,130 endpoint: input.endpoint,
@@ -196,7 +198,7 @@ const createApiClientHarness = () => · 67 unmodified lines
196 const installation = yield* bootstrapClient.local.installation({});198 const installation = yield* bootstrapClient.local.installation({});
197 const client = yield* createExecutorApiClient({199 const client = yield* createExecutorApiClient({
198 baseUrl: server.baseUrl,200 baseUrl: server.baseUrl,
199 accountId: installation.accountId,201 accountId: installation.actorScopeId,
200 });202 });
201 203
202 return {204 return {
@@ -288,161 +290,89 @@ const repoParam = HttpApiSchema.param("repo", Schema.String); · 85 unmodified lines
288class ExecutorDemoReposApi extends HttpApiGroup.make("repos")290class ExecutorDemoReposApi extends HttpApiGroup.make("repos")
289 .add(291 .add(
290 HttpApiEndpoint.get("getRepo")`/repos/${ownerParam}/${repoParam}`292 HttpApiEndpoint.get("getRepo")`/repos/${ownerParam}/${repoParam}`
291 .addSuccess(293 .addSuccess(Schema.Unknown),
292 Schema.Struct({
293 full_name: Schema.String,
294 private: Schema.Boolean,
295 }),
296 ),
297 )294 )
298{}295{}
299 296
300class ExecutorDemoApi extends HttpApi.make("executorDemo").add(ExecutorDemoReposApi) {}297class ExecutorDemoApi extends HttpApi.make("executorDemo").add(ExecutorDemoReposApi) {}
301 298
302const executorDemoOpenApiSpec = OpenApi.fromApi(ExecutorDemoApi);299class ExecutorDnsRecordsApi extends HttpApiGroup.make("records")
300 .add(
301 HttpApiEndpoint.post("createRecord")`/records`
302 .setPayload(
303 Schema.Struct({
304 type: Schema.optional(Schema.String),
305 name: Schema.optional(Schema.String),
306 value: Schema.String,
307 }),
308 )
309 .addSuccess(Schema.Unknown),
310 )
311{}
312
313class ExecutorDnsApi extends HttpApi.make("executorDns").add(
314 ExecutorDnsRecordsApi,
315) {}
303 316
304const startOpenApiDemoServer = async () => {317const startOpenApiDemoServer = async () => {
305 const seenAuthHeaders: Array<string | null> = [];318 const seenAuthHeaders: Array<string | null> = [];
319 const executorDemoLive = HttpApiBuilder.group(
320 ExecutorDemoApi,
321 "repos",
322 (handlers) =>
323 handlers.handle("getRepo", ({ path, request }) => {
324 const authorization = request.headers.authorization;
325 if (typeof authorization === "string") {
326 seenAuthHeaders.push(authorization);
327 } else {
328 seenAuthHeaders.push(null);
329 }
306 330
307 const handler = (req: IncomingMessage, res: ServerResponse) => {331 return Effect.succeed({
308 if (req.url === "/openapi.json") {332 full_name: `${path.owner}/${path.repo}`,
309 res.statusCode = 200;
310 res.setHeader("content-type", "application/json");
311 res.end(JSON.stringify(executorDemoOpenApiSpec));
312 return;
313 }
314
315 const match = req.url?.match(/^\/repos\/([^/]+)\/([^/]+)$/);
316 if (req.method === "GET" && match) {
317 seenAuthHeaders.push(
318 typeof req.headers.authorization === "string" ? req.headers.authorization : null,
319 );
320 res.statusCode = 200;
321 res.setHeader("content-type", "application/json");
322 res.end(
323 JSON.stringify({
324 full_name: `${decodeURIComponent(match[1] ?? "")}/${decodeURIComponent(match[2] ?? "")}`,
325 private: false,333 private: false,
326 }),334 });
327 );335 }),
328 return;336 );
329 }
330
331 res.statusCode = 404;
332 res.end("not found");
333 };
334 337
335 const server = createServer(handler);338 const server = await startOpenApiTestServer({
336 await new Promise<void>((resolve, reject) => {339 apiLayer: HttpApiBuilder.api(ExecutorDemoApi).pipe(
337 server.once("error", reject);340 Layer.provide(executorDemoLive),
338 server.listen(0, "127.0.0.1", () => resolve());341 ),
339 });342 });
340 const address = server.address();
341 if (!address || typeof address === "string") {
342 throw new Error("Failed to bind OpenAPI demo server");
343 }
344 343
345 return {344 return {
346 baseUrl: `http://127.0.0.1:${address.port}`,345 ...server,
347 specUrl: `http://127.0.0.1:${address.port}/openapi.json`,
348 seenAuthHeaders,346 seenAuthHeaders,
349 close: () =>
350 new Promise<void>((resolve, reject) => {
351 server.close((error) => (error ? reject(error) : resolve()));
352 }),
353 };347 };
354};348};
355 349
356const startMutatingOpenApiDemoServer = async () => {350const startMutatingOpenApiDemoServer = async () => {
357 const createdBodies: Array<Record<string, unknown>> = [];351 const createdBodies: Array<Record<string, unknown>> = [];
358 const openApiDocument = JSON.stringify({352 const executorDnsLive = HttpApiBuilder.group(
359 openapi: "3.0.3",353 ExecutorDnsApi,
360 info: {354 "records",
361 title: "Executor DNS Demo API",355 (handlers) =>
362 version: "1.0.0",356 handlers.handle("createRecord", ({ payload }) => {
363 },357 createdBodies.push(payload);
364 paths: {358
365 "/records": {359 return Effect.succeed({
366 post: {
367 operationId: "records.createRecord",
368 tags: ["records"],
369 summary: "Create a DNS record",
370 requestBody: {
371 required: true,
372 content: {
373 "application/json": {
374 schema: {
375 type: "object",
376 properties: {
377 type: { type: "string" },
378 name: { type: "string" },
379 value: { type: "string" },
380 },
381 required: ["type", "value"],
382 },
383 },
384 },
385 },
386 responses: {
387 200: {
388 description: "ok",
389 },
390 },
391 },
392 },
393 },
394 });
395
396 const handler = (req: IncomingMessage, res: ServerResponse) => {
397 if (req.url === "/openapi.json") {
398 res.statusCode = 200;
399 res.setHeader("content-type", "application/json");
400 res.end(openApiDocument);
401 return;
402 }
403
404 if (req.method === "POST" && req.url === "/records") {
405 const chunks: Array<Buffer> = [];
406 req.on("data", (chunk) => {
407 chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
408 });
409 req.on("end", () => {
410 const raw = Buffer.concat(chunks).toString("utf8");
411 const parsed = JSON.parse(raw) as Record<string, unknown>;
412 createdBodies.push(parsed);
413 res.statusCode = 200;
414 res.setHeader("content-type", "application/json");
415 res.end(JSON.stringify({
416 ok: true,360 ok: true,
417 id: `rec_${createdBodies.length}`,361 id: `rec_${createdBodies.length}`,
418 record: parsed,362 record: payload,
419 }));363 });
420 });364 }),
421 return;365 );
422 }
423
424 res.statusCode = 404;
425 res.end("not found");
426 };
427 366
428 const server = createServer(handler);367 const server = await startOpenApiTestServer({
429 await new Promise<void>((resolve, reject) => {368 apiLayer: HttpApiBuilder.api(ExecutorDnsApi).pipe(
430 server.once("error", reject);369 Layer.provide(executorDnsLive),
431 server.listen(0, "127.0.0.1", () => resolve());370 ),
432 });371 });
433 const address = server.address();
434 if (!address || typeof address === "string") {
435 throw new Error("Failed to bind mutating OpenAPI demo server");
436 }
437 372
438 return {373 return {
439 baseUrl: `http://127.0.0.1:${address.port}`,374 ...server,
440 specUrl: `http://127.0.0.1:${address.port}/openapi.json`,
441 createdBodies,375 createdBodies,
442 close: () =>
443 new Promise<void>((resolve, reject) => {
444 server.close((error) => (error ? reject(error) : resolve()));
445 }),
446 };376 };
447};377};
448 378
@@ -750,12 +680,12 @@ describe("local-executor-server", () => { · 301 unmodified lines
750 const installation = yield* bootstrapClient.local.installation({});680 const installation = yield* bootstrapClient.local.installation({});
751 const client = yield* createExecutorApiClient({681 const client = yield* createExecutorApiClient({
752 baseUrl: server.baseUrl,682 baseUrl: server.baseUrl,
753 accountId: installation.accountId,683 accountId: installation.actorScopeId,
754 });684 });
755 685
756 const execution = yield* client.executions.create({686 const execution = yield* client.executions.create({
757 path: {687 path: {
758 workspaceId: installation.workspaceId,688 workspaceId: installation.scopeId,
759 },689 },
760 payload: {690 payload: {
761 code: "return await tools.math.add({ a: 20, b: 22 });",691 code: "return await tools.math.add({ a: 20, b: 22 });",
@@ -951,12 +881,12 @@ describe("local-executor-server", () => { · 189 unmodified lines
951 const installation = yield* bootstrapClient.local.installation({});881 const installation = yield* bootstrapClient.local.installation({});
952 const client = yield* createExecutorApiClient({882 const client = yield* createExecutorApiClient({
953 baseUrl: server.baseUrl,883 baseUrl: server.baseUrl,
954 accountId: installation.accountId,884 accountId: installation.actorScopeId,
955 });885 });
956 886
957 yield* client.sources.create({887 yield* client.sources.create({
958 path: {888 path: {
959 workspaceId: installation.workspaceId,889 workspaceId: installation.scopeId,
960 },890 },
961 payload: {891 payload: {
962 name: "Demo",892 name: "Demo",
@@ -978,7 +908,7 @@ describe("local-executor-server", () => { · 15 unmodified lines
978 908
979 const created = yield* client.executions.create({909 const created = yield* client.executions.create({
980 path: {910 path: {
981 workspaceId: installation.workspaceId,911 workspaceId: installation.scopeId,
982 },912 },
983 payload: {913 payload: {
984 code: 'return await tools.demo.gated_echo({ value: "from-daemon" });',914 code: 'return await tools.demo.gated_echo({ value: "from-daemon" });',
@@ -995,7 +925,7 @@ describe("local-executor-server", () => { · 10 unmodified lines
995 925
996 const approved = yield* client.executions.resume({926 const approved = yield* client.executions.resume({
997 path: {927 path: {
998 workspaceId: installation.workspaceId,928 workspaceId: installation.scopeId,
999 executionId: created.execution.id,929 executionId: created.execution.id,
1000 },930 },
1001 payload: {931 payload: {
@@ -1018,7 +948,7 @@ describe("local-executor-server", () => { · 16 unmodified lines
1018 948
1019 const resumed = yield* client.executions.resume({949 const resumed = yield* client.executions.resume({
1020 path: {950 path: {
1021 workspaceId: installation.workspaceId,951 workspaceId: installation.scopeId,
1022 executionId: created.execution.id,952 executionId: created.execution.id,
1023 },953 },
1024 payload: {954 payload: {
@@ -1057,12 +987,12 @@ describe("local-executor-server", () => { · 32 unmodified lines
1057 const installation = yield* bootstrapClient.local.installation({});987 const installation = yield* bootstrapClient.local.installation({});
1058 const client = yield* createExecutorApiClient({988 const client = yield* createExecutorApiClient({
1059 baseUrl: server.baseUrl,989 baseUrl: server.baseUrl,
1060 accountId: installation.accountId,990 accountId: installation.actorScopeId,
1061 });991 });
1062 992
1063 yield* client.sources.create({993 yield* client.sources.create({
1064 path: {994 path: {
1065 workspaceId: installation.workspaceId,995 workspaceId: installation.scopeId,
1066 },996 },
1067 payload: {997 payload: {
1068 name: "Demo",998 name: "Demo",
@@ -1085,7 +1015,7 @@ describe("local-executor-server", () => { · 16 unmodified lines
1085 for (const value of ["first", "second"]) {1015 for (const value of ["first", "second"]) {
1086 const created = yield* client.executions.create({1016 const created = yield* client.executions.create({
1087 path: {1017 path: {
1088 workspaceId: installation.workspaceId,1018 workspaceId: installation.scopeId,
1089 },1019 },
1090 payload: {1020 payload: {
1091 code: `return await tools.demo.gated_echo({ value: "${value}" });`,1021 code: `return await tools.demo.gated_echo({ value: "${value}" });`,
@@ -1098,7 +1028,7 @@ describe("local-executor-server", () => { · 6 unmodified lines
1098 1028
1099 const approved = yield* client.executions.resume({1029 const approved = yield* client.executions.resume({
1100 path: {1030 path: {
1101 workspaceId: installation.workspaceId,1031 workspaceId: installation.scopeId,
1102 executionId: created.execution.id,1032 executionId: created.execution.id,
1103 },1033 },
1104 payload: {1034 payload: {
@@ -1121,7 +1051,7 @@ describe("local-executor-server", () => { · 16 unmodified lines
1121 1051
1122 const resumed = yield* client.executions.resume({1052 const resumed = yield* client.executions.resume({
1123 path: {1053 path: {
1124 workspaceId: installation.workspaceId,1054 workspaceId: installation.scopeId,
1125 executionId: created.execution.id,1055 executionId: created.execution.id,
1126 },1056 },
1127 payload: {1057 payload: {
@@ -1160,12 +1090,12 @@ describe("local-executor-server", () => { · 32 unmodified lines
1160 const installation = yield* bootstrapClient.local.installation({});1090 const installation = yield* bootstrapClient.local.installation({});
1161 const client = yield* createExecutorApiClient({1091 const client = yield* createExecutorApiClient({
1162 baseUrl: server.baseUrl,1092 baseUrl: server.baseUrl,
1163 accountId: installation.accountId,1093 accountId: installation.actorScopeId,
1164 });1094 });
1165 1095
1166 const existing = yield* client.sources.create({1096 const existing = yield* client.sources.create({
1167 path: {1097 path: {
1168 workspaceId: installation.workspaceId,1098 workspaceId: installation.scopeId,
1169 },1099 },
1170 payload: {1100 payload: {
1171 name: "Demo",1101 name: "Demo",
@@ -1187,7 +1117,7 @@ describe("local-executor-server", () => { · 15 unmodified lines
1187 1117
1188 const seeded = yield* seedDemoMcpSourceInWorkspace({1118 const seeded = yield* seedDemoMcpSourceInWorkspace({
1189 client,1119 client,
1190 workspaceId: installation.workspaceId,1120 workspaceId: installation.scopeId,
1191 endpoint: demoServer.endpoint,1121 endpoint: demoServer.endpoint,
1192 name: "Demo",1122 name: "Demo",
1193 namespace: "demo",1123 namespace: "demo",
@@ -1198,7 +1128,7 @@ describe("local-executor-server", () => { · 4 unmodified lines
1198 1128
1199 const sources = yield* client.sources.list({1129 const sources = yield* client.sources.list({
1200 path: {1130 path: {
1201 workspaceId: installation.workspaceId,1131 workspaceId: installation.scopeId,
1202 },1132 },
1203 });1133 });
1204 1134
@@ -1249,12 +1179,12 @@ describe("local-executor-server", () => { · 44 unmodified lines
1249 const installation = yield* bootstrapClient.local.installation({});1179 const installation = yield* bootstrapClient.local.installation({});
1250 const client = yield* createExecutorApiClient({1180 const client = yield* createExecutorApiClient({
1251 baseUrl: server.baseUrl,1181 baseUrl: server.baseUrl,
1252 accountId: installation.accountId,1182 accountId: installation.actorScopeId,
1253 });1183 });
1254 1184
1255 yield* seedGithubOpenApiSourceInWorkspace({1185 yield* seedGithubOpenApiSourceInWorkspace({
1256 client,1186 client,
1257 workspaceId: installation.workspaceId,1187 workspaceId: installation.scopeId,
1258 endpoint: openApiServer.baseUrl,1188 endpoint: openApiServer.baseUrl,
1259 specUrl: openApiServer.specUrl,1189 specUrl: openApiServer.specUrl,
1260 name: "GitHub",1190 name: "GitHub",
@@ -1263,7 +1193,7 @@ describe("local-executor-server", () => { · 2 unmodified lines
1263 1193
1264 const execution = yield* client.executions.create({1194 const execution = yield* client.executions.create({
1265 path: {1195 path: {
1266 workspaceId: installation.workspaceId,1196 workspaceId: installation.scopeId,
1267 },1197 },
1268 payload: {1198 payload: {
1269 code: 'return await tools.github.repos.getRepo({ owner: "vercel", repo: "ai" });',1199 code: 'return await tools.github.repos.getRepo({ owner: "vercel", repo: "ai" });',
@@ -1287,7 +1217,7 @@ describe("local-executor-server", () => { · 17 unmodified lines
1287 1217
1288 const connected = yield* client.sources.connect({1218 const connected = yield* client.sources.connect({
1289 path: {1219 path: {
1290 workspaceId: installation.workspaceId,1220 workspaceId: installation.scopeId,
1291 },1221 },
1292 payload: {1222 payload: {
1293 kind: "openapi",1223 kind: "openapi",
@@ -1309,7 +1239,7 @@ describe("local-executor-server", () => { · 15 unmodified lines
1309 1239
1310 const sources = yield* client.sources.list({1240 const sources = yield* client.sources.list({
1311 path: {1241 path: {
1312 workspaceId: installation.workspaceId,1242 workspaceId: installation.scopeId,
1313 },1243 },
1314 });1244 });
1315 1245
@@ -1318,7 +1248,7 @@ describe("local-executor-server", () => { · 2 unmodified lines
1318 1248
1319 const execution = yield* client.executions.create({1249 const execution = yield* client.executions.create({
1320 path: {1250 path: {
1321 workspaceId: installation.workspaceId,1251 workspaceId: installation.scopeId,
1322 },1252 },
1323 payload: {1253 payload: {
1324 code: 'return await tools.github.repos.getRepo({ owner: "vercel", repo: "ai" });',1254 code: 'return await tools.github.repos.getRepo({ owner: "vercel", repo: "ai" });',
@@ -1343,7 +1273,7 @@ describe("local-executor-server", () => { · 18 unmodified lines
1343 1273
1344 const connected = yield* client.sources.connect({1274 const connected = yield* client.sources.connect({
1345 path: {1275 path: {
1346 workspaceId: installation.workspaceId,1276 workspaceId: installation.scopeId,
1347 },1277 },
1348 payload: {1278 payload: {
1349 kind: "mcp",1279 kind: "mcp",
@@ -1361,7 +1291,7 @@ describe("local-executor-server", () => { · 11 unmodified lines
1361 1291
1362 const execution = yield* client.executions.create({1292 const execution = yield* client.executions.create({
1363 path: {1293 path: {
1364 workspaceId: installation.workspaceId,1294 workspaceId: installation.scopeId,
1365 },1295 },
1366 payload: {1296 payload: {
1367 code: 'return await tools.demo.gated_echo({ value: "from-api-client" });',1297 code: 'return await tools.demo.gated_echo({ value: "from-api-client" });',
@@ -1379,7 +1309,7 @@ describe("local-executor-server", () => { · 11 unmodified lines
1379 1309
1380 const approved = yield* client.executions.resume({1310 const approved = yield* client.executions.resume({
1381 path: {1311 path: {
1382 workspaceId: installation.workspaceId,1312 workspaceId: installation.scopeId,
1383 executionId: execution.execution.id,1313 executionId: execution.execution.id,
1384 },1314 },
1385 payload: {1315 payload: {
@@ -1405,7 +1335,7 @@ describe("local-executor-server", () => { · 19 unmodified lines
1405 1335
1406 const resumed = yield* client.executions.resume({1336 const resumed = yield* client.executions.resume({
1407 path: {1337 path: {
1408 workspaceId: installation.workspaceId,1338 workspaceId: installation.scopeId,
1409 executionId: execution.execution.id,1339 executionId: execution.execution.id,
1410 },1340 },
1411 payload: {1341 payload: {
@@ -1436,7 +1366,7 @@ describe("local-executor-server", () => { · 24 unmodified lines
1436 1366
1437 const connected = yield* client.sources.connect({1367 const connected = yield* client.sources.connect({
1438 path: {1368 path: {
1439 workspaceId: installation.workspaceId,1369 workspaceId: installation.scopeId,
1440 },1370 },
1441 payload: {1371 payload: {
1442 kind: "mcp",1372 kind: "mcp",
@@ -1463,7 +1393,7 @@ describe("local-executor-server", () => { · 20 unmodified lines
1463 1393
1464 const refreshedSource = yield* client.sources.get({1394 const refreshedSource = yield* client.sources.get({
1465 path: {1395 path: {
1466 workspaceId: installation.workspaceId,1396 workspaceId: installation.scopeId,
1467 sourceId: connected.source.id,1397 sourceId: connected.source.id,
1468 },1398 },
1469 });1399 });
@@ -1474,7 +1404,7 @@ describe("local-executor-server", () => { · 4 unmodified lines
1474 1404
1475 const toolCall = yield* client.executions.create({1405 const toolCall = yield* client.executions.create({
1476 path: {1406 path: {
1477 workspaceId: installation.workspaceId,1407 workspaceId: installation.scopeId,
1478 },1408 },
1479 payload: {1409 payload: {
1480 code: "return await tools.axiom.whoami({});",1410 code: "return await tools.axiom.whoami({});",
@@ -1491,7 +1421,7 @@ describe("local-executor-server", () => { · 10 unmodified lines
1491 1421
1492 const approvedToolCall = yield* client.executions.resume({1422 const approvedToolCall = yield* client.executions.resume({
1493 path: {1423 path: {
1494 workspaceId: installation.workspaceId,1424 workspaceId: installation.scopeId,
1495 executionId: toolCall.execution.id,1425 executionId: toolCall.execution.id,
1496 },1426 },
1497 payload: {1427 payload: {
@@ -1529,12 +1459,12 @@ describe("local-executor-server", () => { · 31 unmodified lines
1529 const installation = yield* bootstrapClient.local.installation({});1459 const installation = yield* bootstrapClient.local.installation({});
1530 const client = yield* createExecutorApiClient({1460 const client = yield* createExecutorApiClient({
1531 baseUrl: server.baseUrl,1461 baseUrl: server.baseUrl,
1532 accountId: installation.accountId,1462 accountId: installation.actorScopeId,
1533 });1463 });
1534 1464
1535 const added = yield* client.executions.create({1465 const added = yield* client.executions.create({
1536 path: {1466 path: {
1537 workspaceId: installation.workspaceId,1467 workspaceId: installation.scopeId,
1538 },1468 },
1539 payload: {1469 payload: {
1540 code: `return await tools.executor.sources.add({ endpoint: ${JSON.stringify(oauthServer.endpoint)}, name: "Axiom", namespace: "axiom" });`,1470 code: `return await tools.executor.sources.add({ endpoint: ${JSON.stringify(oauthServer.endpoint)}, name: "Axiom", namespace: "axiom" });`,
@@ -1563,7 +1493,7 @@ describe("local-executor-server", () => { · 22 unmodified lines
1563 while (true) {1493 while (true) {
1564 const sources = yield* client.sources.list({1494 const sources = yield* client.sources.list({
1565 path: {1495 path: {
1566 workspaceId: installation.workspaceId,1496 workspaceId: installation.scopeId,
1567 },1497 },
1568 });1498 });
1569 const source = sources.find((entry) => entry.namespace === "axiom");1499 const source = sources.find((entry) => entry.namespace === "axiom");
@@ -1581,7 +1511,7 @@ describe("local-executor-server", () => { · 11 unmodified lines
1581 1511
1582 const toolCall = yield* client.executions.create({1512 const toolCall = yield* client.executions.create({
1583 path: {1513 path: {
1584 workspaceId: installation.workspaceId,1514 workspaceId: installation.scopeId,
1585 },1515 },
1586 payload: {1516 payload: {
1587 code: "return await tools.axiom.whoami({});",1517 code: "return await tools.axiom.whoami({});",
@@ -1597,7 +1527,7 @@ describe("local-executor-server", () => { · 9 unmodified lines
1597 1527
1598 const approvedToolCall = yield* client.executions.resume({1528 const approvedToolCall = yield* client.executions.resume({
1599 path: {1529 path: {
1600 workspaceId: installation.workspaceId,1530 workspaceId: installation.scopeId,
1601 executionId: toolCall.execution.id,1531 executionId: toolCall.execution.id,
1602 },1532 },
1603 payload: {1533 payload: {
@@ -1633,12 +1563,12 @@ describe("local-executor-server", () => { · 29 unmodified lines
1633 const installation = yield* bootstrapClient.local.installation({});1563 const installation = yield* bootstrapClient.local.installation({});
1634 const client = yield* createExecutorApiClient({1564 const client = yield* createExecutorApiClient({
1635 baseUrl: server.baseUrl,1565 baseUrl: server.baseUrl,
1636 accountId: installation.accountId,1566 accountId: installation.actorScopeId,
1637 });1567 });
1638 1568
1639 const connected = yield* client.sources.connect({1569 const connected = yield* client.sources.connect({
1640 path: {1570 path: {
1641 workspaceId: installation.workspaceId,1571 workspaceId: installation.scopeId,
1642 },1572 },
1643 payload: {1573 payload: {
1644 kind: "openapi",1574 kind: "openapi",
@@ -1655,7 +1585,7 @@ describe("local-executor-server", () => { · 10 unmodified lines
1655 1585
1656 const gated = yield* client.executions.create({1586 const gated = yield* client.executions.create({
1657 path: {1587 path: {
1658 workspaceId: installation.workspaceId,1588 workspaceId: installation.scopeId,
1659 },1589 },
1660 payload: {1590 payload: {
1661 code: 'return await tools.dns.records.createRecord({ body: { type: "TXT", name: "", value: "hello world" } });',1591 code: 'return await tools.dns.records.createRecord({ body: { type: "TXT", name: "", value: "hello world" } });',
@@ -1668,12 +1598,12 @@ describe("local-executor-server", () => { · 6 unmodified lines
1668 throw new Error("Expected pending approval interaction");1598 throw new Error("Expected pending approval interaction");
1669 }1599 }
1670 expect(gated.pendingInteraction.kind).toBe("form");1600 expect(gated.pendingInteraction.kind).toBe("form");
1671 expect(gated.pendingInteraction.payloadJson).toContain("Allow Create a DNS record?");1601 expect(gated.pendingInteraction.payloadJson).toContain("Allow records.createRecord?");
1672 expect(gated.pendingInteraction.payloadJson).toContain("\"approve\"");1602 expect(gated.pendingInteraction.payloadJson).toContain("\"approve\"");
1673 1603
1674 const approved = yield* client.executions.resume({1604 const approved = yield* client.executions.resume({
1675 path: {1605 path: {
1676 workspaceId: installation.workspaceId,1606 workspaceId: installation.scopeId,
1677 executionId: gated.execution.id,1607 executionId: gated.execution.id,
1678 },1608 },
1679 payload: {1609 payload: {
@@ -1691,7 +1621,7 @@ describe("local-executor-server", () => { · 11 unmodified lines
1691 1621
1692 const policy = yield* client.policies.create({1622 const policy = yield* client.policies.create({
1693 path: {1623 path: {
1694 workspaceId: installation.workspaceId,1624 workspaceId: installation.scopeId,
1695 },1625 },
1696 payload: {1626 payload: {
1697 resourcePattern: "dns.records.createRecord",1627 resourcePattern: "dns.records.createRecord",
@@ -1703,7 +1633,7 @@ describe("local-executor-server", () => { · 5 unmodified lines
1703 1633
1704 const automatic = yield* client.executions.create({1634 const automatic = yield* client.executions.create({
1705 path: {1635 path: {
1706 workspaceId: installation.workspaceId,1636 workspaceId: installation.scopeId,
1707 },1637 },
1708 payload: {1638 payload: {
1709 code: 'return await tools.dns.records.createRecord({ body: { type: "TXT", name: "", value: "hello again" } });',1639 code: 'return await tools.dns.records.createRecord({ body: { type: "TXT", name: "", value: "hello again" } });',
@@ -1735,11 +1665,11 @@ describe("local-executor-server", () => { · 25 unmodified lines
1735 const installation = yield* bootstrapClient.local.installation({});1665 const installation = yield* bootstrapClient.local.installation({});
1736 1666
1737 const startResponse = yield* Effect.promise(() =>1667 const startResponse = yield* Effect.promise(() =>
1738 fetch(`${server.baseUrl}/v1/workspaces/${encodeURIComponent(installation.workspaceId)}/oauth/source-auth/start`, {1668 fetch(`${server.baseUrl}/v1/workspaces/${encodeURIComponent(installation.scopeId)}/oauth/source-auth/start`, {
1739 method: "POST",1669 method: "POST",
1740 headers: {1670 headers: {
1741 "content-type": "application/json",1671 "content-type": "application/json",
1742 "x-executor-account-id": installation.accountId,1672 "x-executor-account-id": installation.actorScopeId,
1743 },1673 },
1744 body: JSON.stringify({1674 body: JSON.stringify({
1745 provider: "mcp",1675 provider: "mcp",
@@ -1774,11 +1704,11 @@ describe("local-executor-server", () => { · 28 unmodified lines
1774 1704
1775 const client = yield* createExecutorApiClient({1705 const client = yield* createExecutorApiClient({
1776 baseUrl: server.baseUrl,1706 baseUrl: server.baseUrl,
1777 accountId: installation.accountId,1707 accountId: installation.actorScopeId,
1778 });1708 });
1779 const sources = yield* client.sources.list({1709 const sources = yield* client.sources.list({
1780 path: {1710 path: {
1781 workspaceId: installation.workspaceId,1711 workspaceId: installation.scopeId,
1782 },1712 },
1783 });1713 });
1784 1714
@@ -1804,7 +1734,7 @@ describe("local-executor-server", () => { · 19 unmodified lines
1804 const installation = yield* bootstrapClient.local.installation({});1734 const installation = yield* bootstrapClient.local.installation({});
1805 const client = yield* createExecutorApiClient({1735 const client = yield* createExecutorApiClient({
1806 baseUrl: server.baseUrl,1736 baseUrl: server.baseUrl,
1807 accountId: installation.accountId,1737 accountId: installation.actorScopeId,
1808 });1738 });
1809 yield* writeConfiguredLocalMcpSource({1739 yield* writeConfiguredLocalMcpSource({
1810 workspaceRoot: server.workspaceRoot,1740 workspaceRoot: server.workspaceRoot,
@@ -1816,7 +1746,7 @@ describe("local-executor-server", () => { · 5 unmodified lines
1816 1746
1817 const execution = yield* client.executions.create({1747 const execution = yield* client.executions.create({
1818 path: {1748 path: {
1819 workspaceId: installation.workspaceId,1749 workspaceId: installation.scopeId,
1820 },1750 },
1821 payload: {1751 payload: {
1822 code: 'return await tools.demo.gated_echo({ value: "broken" });',1752 code: 'return await tools.demo.gated_echo({ value: "broken" });',
@@ -1832,7 +1762,7 @@ describe("local-executor-server", () => { · 9 unmodified lines
1832 1762
1833 const resumed = yield* client.executions.resume({1763 const resumed = yield* client.executions.resume({
1834 path: {1764 path: {
1835 workspaceId: installation.workspaceId,1765 workspaceId: installation.scopeId,
1836 executionId: execution.execution.id,1766 executionId: execution.execution.id,
1837 },1767 },
1838 payload: {1768 payload: {
apps/web/src/views/add-source.tsx+2−2
@@ -484,7 +484,7 @@ const buildConnectPayload = (form: ConnectFormState): ConnectSourcePayload => { · 483 unmodified lines
484 discoveryUrl: trimToNull(form.discoveryUrl),484 discoveryUrl: trimToNull(form.discoveryUrl),
485 name: trimToNull(form.name),485 name: trimToNull(form.name),
486 namespace: trimToNull(form.namespace),486 namespace: trimToNull(form.namespace),
487 workspaceOauthClientId:487 scopeOauthClientId:
488 form.authKind === "oauth2" &&488 form.authKind === "oauth2" &&
489 form.workspaceOauthClientId.trim().length > 0489 form.workspaceOauthClientId.trim().length > 0
490 ? form.workspaceOauthClientId.trim()490 ? form.workspaceOauthClientId.trim()
@@ -1114,7 +1114,7 @@ export function AddSourcePage() { · 623 unmodified lines
1114 selectedGoogleTemplateIds.includes(template.id),1114 selectedGoogleTemplateIds.includes(template.id),
1115 );1115 );
1116 const payload: ConnectSourceBatchPayload = {1116 const payload: ConnectSourceBatchPayload = {
1117 workspaceOauthClientId:1117 scopeOauthClientId:
1118 workspaceOauthClientId as WorkspaceOauthClient["id"],1118 workspaceOauthClientId as WorkspaceOauthClient["id"],
1119 sources: selectedTemplates.map((template) => ({1119 sources: selectedTemplates.map((template) => ({
1120 service: template.service,1120 service: template.service,
bun.lock+19−0
@@ -36,6 +36,7 @@ · 35 unmodified lines
36 },36 },
37 "devDependencies": {37 "devDependencies": {
38 "@effect/vitest": "catalog:",38 "@effect/vitest": "catalog:",
39 "@executor/effect-test-utils": "workspace:*",
39 "@executor/mcp-elicitation-demo": "workspace:*",40 "@executor/mcp-elicitation-demo": "workspace:*",
40 "@executor/runtime-ses": "workspace:*",41 "@executor/runtime-ses": "workspace:*",
41 "@modelcontextprotocol/sdk": "^1.26.0",42 "@modelcontextprotocol/sdk": "^1.26.0",
@@ -150,6 +151,7 @@ · 108 unmodified lines
150 "@effect/platform": "catalog:",151 "@effect/platform": "catalog:",
151 "@effect/platform-node": "catalog:",152 "@effect/platform-node": "catalog:",
152 "@effect/vitest": "catalog:",153 "@effect/vitest": "catalog:",
154 "@executor/effect-test-utils": "workspace:*",
153 "@executor/platform-sdk-file": "workspace:*",155 "@executor/platform-sdk-file": "workspace:*",
154 "@types/jsdom": "^21.1.7",156 "@types/jsdom": "^21.1.7",
155 "@types/node": "catalog:",157 "@types/node": "catalog:",
@@ -164,6 +166,20 @@ · 8 unmodified lines
164 "react": ">=18 <20",166 "react": ">=18 <20",
165 },167 },
166 },168 },
169 "packages/dev/effect-test-utils": {
170 "name": "@executor/effect-test-utils",
171 "version": "0.0.0",
172 "dependencies": {
173 "@effect/platform": "catalog:",
174 "@effect/platform-node": "catalog:",
175 "effect": "catalog:",
176 },
177 "devDependencies": {
178 "@types/node": "catalog:",
179 "bun-types": "catalog:",
180 "typescript": "^5.9.3",
181 },
182 },
167 "packages/dev/kitchen-sink": {183 "packages/dev/kitchen-sink": {
168 "name": "@executor/kitchen-sink",184 "name": "@executor/kitchen-sink",
169 "version": "0.0.0",185 "version": "0.0.0",
@@ -337,6 +353,7 @@ · 167 unmodified lines
337 },353 },
338 "devDependencies": {354 "devDependencies": {
339 "@effect/vitest": "catalog:",355 "@effect/vitest": "catalog:",
356 "@executor/effect-test-utils": "workspace:*",
340 "@modelcontextprotocol/sdk": "^1.26.0",357 "@modelcontextprotocol/sdk": "^1.26.0",
341 "@types/node": "catalog:",358 "@types/node": "catalog:",
342 "bun-types": "catalog:",359 "bun-types": "catalog:",
@@ -709,6 +726,8 @@ · 366 unmodified lines
709 726
710 "@executor/docs": ["@executor/docs@workspace:apps/docs"],727 "@executor/docs": ["@executor/docs@workspace:apps/docs"],
711 728
729 "@executor/effect-test-utils": ["@executor/effect-test-utils@workspace:packages/dev/effect-test-utils"],
730
712 "@executor/executor-mcp": ["@executor/executor-mcp@workspace:packages/hosts/mcp"],731 "@executor/executor-mcp": ["@executor/executor-mcp@workspace:packages/hosts/mcp"],
713 732
714 "@executor/ir": ["@executor/ir@workspace:packages/kernel/ir"],733 "@executor/ir": ["@executor/ir@workspace:packages/kernel/ir"],
examples/sqlite-sdk-consumer/src/main.ts+2−2
@@ -5,8 +5,8 @@ const databasePath = process.env.DATABASE_PATH · 4 unmodified lines
5 5
6const executor = await createSqliteExecutor({6const executor = await createSqliteExecutor({
7 databasePath,7 databasePath,
8 workspaceName: "SQLite Example Workspace",8 scopeName: "SQLite Example Workspace",
9 workspaceRoot: process.cwd(),9 scopeRoot: process.cwd(),
10});10});
11 11
12try {12try {
examples/sqlite-sdk-consumer/src/sqlite-backend.ts+84−81
@@ -16,13 +16,13 @@ import { · 15 unmodified lines
16 type ExecutorSecretMaterialBackend,16 type ExecutorSecretMaterialBackend,
17 type ExecutorSourceArtifactBackend,17 type ExecutorSourceArtifactBackend,
18 type ExecutorSourceTypeDeclarationsBackend,18 type ExecutorSourceTypeDeclarationsBackend,
19 type ExecutorWorkspaceConfigBackend,19 type ExecutorScopeConfigBackend,
20 type ExecutorWorkspaceStateBackend,20 type ExecutorScopeStateBackend,
21} from "@executor/platform-sdk";21} from "@executor/platform-sdk";
22import type { ExecutorRuntimeOptions } from "@executor/platform-sdk/runtime";22import type { ExecutorRuntimeOptions } from "@executor/platform-sdk/runtime";
23import {23import {
24 type AuthArtifact,24 type AuthArtifact,
25 AccountIdSchema,25 ScopeIdSchema,
26 AuthArtifactSchema,26 AuthArtifactSchema,
27 type AuthLease,27 type AuthLease,
28 AuthLeaseSchema,28 AuthLeaseSchema,
@@ -43,11 +43,10 @@ import { · 14 unmodified lines
43 SourceAuthSessionSchema,43 SourceAuthSessionSchema,
44 SourceCatalogIdSchema,44 SourceCatalogIdSchema,
45 SourceCatalogRevisionIdSchema,45 SourceCatalogRevisionIdSchema,
46 WorkspaceIdSchema,46 type ScopeOauthClient,
47 type WorkspaceOauthClient,47 ScopeOauthClientSchema,
48 WorkspaceOauthClientSchema,48 type ScopedSourceOauthClient,
49 type WorkspaceSourceOauthClient,49 ScopedSourceOauthClientSchema,
50 WorkspaceSourceOauthClientSchema,
51 type SecretMaterial,50 type SecretMaterial,
52 type SecretRef,51 type SecretRef,
53} from "@executor/platform-sdk/schema";52} from "@executor/platform-sdk/schema";
@@ -59,24 +58,24 @@ import { contentHash, snapshotFromSourceCatalogSyncResult } from "@executor/sour · 5 unmodified lines
59 58
60export type CreateSqliteExecutorBackendOptions = {59export type CreateSqliteExecutorBackendOptions = {
61 databasePath?: string;60 databasePath?: string;
62 workspaceName?: string;61 scopeName?: string;
63 workspaceRoot?: string | null;62 scopeRoot?: string | null;
64 workspaceId?: string;63 scopeId?: string;
65 accountId?: string;64 actorScopeId?: string;
66};65};
67 66
68type DocumentNamespace =67type DocumentNamespace =
69 | "installation"68 | "installation"
70 | "workspace_config"69 | "scope_config"
71 | "workspace_state"70 | "scope_state"
72 | "source_artifact"71 | "source_artifact"
73 | "executor_state";72 | "executor_state";
74 73
75type ExecutorStateCollections = {74type ExecutorStateCollections = {
76 authArtifacts: readonly AuthArtifact[];75 authArtifacts: readonly AuthArtifact[];
77 authLeases: readonly AuthLease[];76 authLeases: readonly AuthLease[];
78 sourceOauthClients: readonly WorkspaceSourceOauthClient[];77 sourceOauthClients: readonly ScopedSourceOauthClient[];
79 workspaceOauthClients: readonly WorkspaceOauthClient[];78 scopeOauthClients: readonly ScopeOauthClient[];
80 providerAuthGrants: readonly ProviderAuthGrant[];79 providerAuthGrants: readonly ProviderAuthGrant[];
81 sourceAuthSessions: readonly SourceAuthSession[];80 sourceAuthSessions: readonly SourceAuthSession[];
82 secretMaterials: readonly SecretMaterial[];81 secretMaterials: readonly SecretMaterial[];
@@ -90,10 +89,10 @@ const decodeConfig = Schema.decodeUnknownSync(LocalExecutorConfigSchema); · 7 unmodified lines
90const decodeAuthArtifacts = Schema.decodeUnknownSync(Schema.Array(AuthArtifactSchema));89const decodeAuthArtifacts = Schema.decodeUnknownSync(Schema.Array(AuthArtifactSchema));
91const decodeAuthLeases = Schema.decodeUnknownSync(Schema.Array(AuthLeaseSchema));90const decodeAuthLeases = Schema.decodeUnknownSync(Schema.Array(AuthLeaseSchema));
92const decodeSourceOauthClients = Schema.decodeUnknownSync(91const decodeSourceOauthClients = Schema.decodeUnknownSync(
93 Schema.Array(WorkspaceSourceOauthClientSchema),92 Schema.Array(ScopedSourceOauthClientSchema),
94);93);
95const decodeWorkspaceOauthClients = Schema.decodeUnknownSync(94const decodeScopeOauthClients = Schema.decodeUnknownSync(
96 Schema.Array(WorkspaceOauthClientSchema),95 Schema.Array(ScopeOauthClientSchema),
97);96);
98const decodeProviderAuthGrants = Schema.decodeUnknownSync(97const decodeProviderAuthGrants = Schema.decodeUnknownSync(
99 Schema.Array(ProviderAuthGrantSchema),98 Schema.Array(ProviderAuthGrantSchema),
@@ -117,10 +116,10 @@ type SourceArtifactBuildInput = Parameters<ExecutorSourceArtifactBackend["build" · 17 unmodified lines
117 116
118const SQLITE_SECRET_PROVIDER_ID = "sqlite";117const SQLITE_SECRET_PROVIDER_ID = "sqlite";
119 118
120type WorkspaceState = Parameters<ExecutorWorkspaceStateBackend["write"]>[0];119type ScopeState = Parameters<ExecutorScopeStateBackend["write"]>[0];
121const defaultProjectConfig = decodeConfig({});120const defaultProjectConfig = decodeConfig({});
122 121
123const defaultWorkspaceState = (): WorkspaceState => ({122const defaultScopeState = (): ScopeState => ({
124 version: 1 as const,123 version: 1 as const,
125 sources: {},124 sources: {},
126 policies: {},125 policies: {},
@@ -258,8 +257,12 @@ const createInstallationStore = ( · 131 unmodified lines
258): ExecutorInstallationBackend => {257): ExecutorInstallationBackend => {
259 const provision = (): LocalInstallation =>258 const provision = (): LocalInstallation =>
260 decodeInstallation({259 decodeInstallation({
261 accountId: AccountIdSchema.make(options.accountId ?? "acct_sqlite_example"),260 actorScopeId: ScopeIdSchema.make(options.actorScopeId ?? "acct_sqlite_example"),
262 workspaceId: WorkspaceIdSchema.make(options.workspaceId ?? "ws_sqlite_example"),261 scopeId: ScopeIdSchema.make(options.scopeId ?? "ws_sqlite_example"),
262 resolutionScopeIds: [
263 ScopeIdSchema.make(options.scopeId ?? "ws_sqlite_example"),
264 ScopeIdSchema.make(options.actorScopeId ?? "acct_sqlite_example"),
265 ],
263 });266 });
264 267
265 return {268 return {
@@ -276,13 +279,13 @@ const createInstallationStore = ( · 10 unmodified lines
276 };279 };
277};280};
278 281
279const createWorkspaceConfigStore = (282const createScopeConfigStore = (
280 store: SqliteDocumentStore,283 store: SqliteDocumentStore,
281): ExecutorWorkspaceConfigBackend => ({284): ExecutorScopeConfigBackend => ({
282 load: () =>285 load: () =>
283 effectTry("load workspace config", () => {286 effectTry("load workspace config", () => {
284 const projectConfig = store.read(287 const projectConfig = store.read(
285 "workspace_config",288 "scope_config",
286 "project",289 "project",
287 decodeConfig,290 decodeConfig,
288 () => defaultProjectConfig,291 () => defaultProjectConfig,
@@ -296,22 +299,22 @@ const createWorkspaceConfigStore = ( · 7 unmodified lines
296 }),299 }),
297 writeProject: (config) =>300 writeProject: (config) =>
298 effectTry("write workspace config", () => {301 effectTry("write workspace config", () => {
299 store.write("workspace_config", "project", config);302 store.write("scope_config", "project", config);
300 }),303 }),
301 resolveRelativePath: ({ path, workspaceRoot }) =>304 resolveRelativePath: ({ path, scopeRoot }) =>
302 resolvePath(workspaceRoot, path),305 resolvePath(scopeRoot, path),
303});306});
304 307
305const createWorkspaceStateStore = (308const createScopeStateStore = (
306 store: SqliteDocumentStore,309 store: SqliteDocumentStore,
307): ExecutorWorkspaceStateBackend => ({310): ExecutorScopeStateBackend => ({
308 load: () =>311 load: () =>
309 effectTry("load workspace state", () =>312 effectTry("load workspace state", () =>
310 store.read("workspace_state", "active", (value) => value as ReturnType<typeof defaultWorkspaceState>, defaultWorkspaceState),313 store.read("scope_state", "active", (value) => value as ReturnType<typeof defaultScopeState>, defaultScopeState),
311 ),314 ),
312 write: (state) =>315 write: (state) =>
313 effectTry("write workspace state", () => {316 effectTry("write workspace state", () => {
314 store.write("workspace_state", "active", state);317 store.write("scope_state", "active", state);
315 }),318 }),
316});319});
317 320
@@ -351,8 +354,8 @@ const createExecutorStateStore = ( · 33 unmodified lines
351 return store.read("executor_state", key, decodeAuthLeases, () => []) as ExecutorStateCollections[K];354 return store.read("executor_state", key, decodeAuthLeases, () => []) as ExecutorStateCollections[K];
352 case "sourceOauthClients":355 case "sourceOauthClients":
353 return store.read("executor_state", key, decodeSourceOauthClients, () => []) as ExecutorStateCollections[K];356 return store.read("executor_state", key, decodeSourceOauthClients, () => []) as ExecutorStateCollections[K];
354 case "workspaceOauthClients":357 case "scopeOauthClients":
355 return store.read("executor_state", key, decodeWorkspaceOauthClients, () => []) as ExecutorStateCollections[K];358 return store.read("executor_state", key, decodeScopeOauthClients, () => []) as ExecutorStateCollections[K];
356 case "providerAuthGrants":359 case "providerAuthGrants":
357 return store.read("executor_state", key, decodeProviderAuthGrants, () => []) as ExecutorStateCollections[K];360 return store.read("executor_state", key, decodeProviderAuthGrants, () => []) as ExecutorStateCollections[K];
358 case "sourceAuthSessions":361 case "sourceAuthSessions":
@@ -387,23 +390,23 @@ const createExecutorStateStore = ( · 28 unmodified lines
387 390
388 return {391 return {
389 authArtifacts: {392 authArtifacts: {
390 listByWorkspaceId: (workspaceId) =>393 listByScopeId: (scopeId) =>
391 effectTry("list auth artifacts by workspace", () =>394 effectTry("list auth artifacts by workspace", () =>
392 readCollection("authArtifacts").filter((item) => item.workspaceId === workspaceId),395 readCollection("authArtifacts").filter((item) => item.scopeId === scopeId),
393 ),396 ),
394 listByWorkspaceAndSourceId: ({ workspaceId, sourceId }) =>397 listByScopeAndSourceId: ({ scopeId, sourceId }) =>
395 effectTry("list auth artifacts by source", () =>398 effectTry("list auth artifacts by source", () =>
396 readCollection("authArtifacts").filter((item) =>399 readCollection("authArtifacts").filter((item) =>
397 item.workspaceId === workspaceId && item.sourceId === sourceId400 item.scopeId === scopeId && item.sourceId === sourceId
398 ),401 ),
399 ),402 ),
400 getByWorkspaceSourceAndActor: ({ workspaceId, sourceId, actorAccountId, slot }) =>403 getByScopeSourceAndActor: ({ scopeId, sourceId, actorScopeId, slot }) =>
401 effectTry("get auth artifact", () =>404 effectTry("get auth artifact", () =>
402 Option.fromNullable(405 Option.fromNullable(
403 readCollection("authArtifacts").find((item) =>406 readCollection("authArtifacts").find((item) =>
404 item.workspaceId === workspaceId407 item.scopeId === scopeId
405 && item.sourceId === sourceId408 && item.sourceId === sourceId
406 && sameActor(item.actorAccountId, actorAccountId)409 && sameActor(item.actorScopeId, actorScopeId)
407 && item.slot === slot410 && item.slot === slot
408 ),411 ),
409 ),412 ),
@@ -412,9 +415,9 @@ const createExecutorStateStore = ( · 2 unmodified lines
412 effectTry("upsert auth artifact", () => {415 effectTry("upsert auth artifact", () => {
413 updateCollection("authArtifacts", (items) => {416 updateCollection("authArtifacts", (items) => {
414 const index = items.findIndex((item) =>417 const index = items.findIndex((item) =>
415 item.workspaceId === artifact.workspaceId418 item.scopeId === artifact.scopeId
416 && item.sourceId === artifact.sourceId419 && item.sourceId === artifact.sourceId
417 && sameActor(item.actorAccountId, artifact.actorAccountId)420 && sameActor(item.actorScopeId, artifact.actorScopeId)
418 && item.slot === artifact.slot421 && item.slot === artifact.slot
419 );422 );
420 if (index >= 0) {423 if (index >= 0) {
@@ -424,25 +427,25 @@ const createExecutorStateStore = ( · 3 unmodified lines
424 }427 }
425 });428 });
426 }),429 }),
427 removeByWorkspaceSourceAndActor: ({ workspaceId, sourceId, actorAccountId, slot }) =>430 removeByScopeSourceAndActor: ({ scopeId, sourceId, actorScopeId, slot }) =>
428 effectTry("remove auth artifact", () =>431 effectTry("remove auth artifact", () =>
429 updateCollection("authArtifacts", (items) => {432 updateCollection("authArtifacts", (items) => {
430 const before = items.length;433 const before = items.length;
431 const remaining = items.filter((item) => !(434 const remaining = items.filter((item) => !(
432 item.workspaceId === workspaceId435 item.scopeId === scopeId
433 && item.sourceId === sourceId436 && item.sourceId === sourceId
434 && sameActor(item.actorAccountId, actorAccountId)437 && sameActor(item.actorScopeId, actorScopeId)
435 && (slot === undefined || item.slot === slot)438 && (slot === undefined || item.slot === slot)
436 ));439 ));
437 items.splice(0, items.length, ...remaining);440 items.splice(0, items.length, ...remaining);
438 return before !== remaining.length;441 return before !== remaining.length;
439 }),442 }),
440 ),443 ),
441 removeByWorkspaceAndSourceId: ({ workspaceId, sourceId }) =>444 removeByScopeAndSourceId: ({ scopeId, sourceId }) =>
442 effectTry("remove auth artifacts by source", () =>445 effectTry("remove auth artifacts by source", () =>
443 updateCollection("authArtifacts", (items) => {446 updateCollection("authArtifacts", (items) => {
444 const remaining = items.filter((item) =>447 const remaining = items.filter((item) =>
445 !(item.workspaceId === workspaceId && item.sourceId === sourceId)448 !(item.scopeId === scopeId && item.sourceId === sourceId)
446 );449 );
447 const removed = items.length - remaining.length;450 const removed = items.length - remaining.length;
448 items.splice(0, items.length, ...remaining);451 items.splice(0, items.length, ...remaining);
@@ -481,11 +484,11 @@ const createExecutorStateStore = ( · 32 unmodified lines
481 ),484 ),
482 },485 },
483 sourceOauthClients: {486 sourceOauthClients: {
484 getByWorkspaceSourceAndProvider: ({ workspaceId, sourceId, providerKey }) =>487 getByScopeSourceAndProvider: ({ scopeId, sourceId, providerKey }) =>
485 effectTry("get source oauth client", () =>488 effectTry("get source oauth client", () =>
486 Option.fromNullable(489 Option.fromNullable(
487 readCollection("sourceOauthClients").find((item) =>490 readCollection("sourceOauthClients").find((item) =>
488 item.workspaceId === workspaceId491 item.scopeId === scopeId
489 && item.sourceId === sourceId492 && item.sourceId === sourceId
490 && item.providerKey === providerKey493 && item.providerKey === providerKey
491 ),494 ),
@@ -495,7 +498,7 @@ const createExecutorStateStore = ( · 3 unmodified lines
495 effectTry("upsert source oauth client", () => {498 effectTry("upsert source oauth client", () => {
496 updateCollection("sourceOauthClients", (items) => {499 updateCollection("sourceOauthClients", (items) => {
497 const index = items.findIndex((item) =>500 const index = items.findIndex((item) =>
498 item.workspaceId === oauthClient.workspaceId501 item.scopeId === oauthClient.scopeId
499 && item.sourceId === oauthClient.sourceId502 && item.sourceId === oauthClient.sourceId
500 && item.providerKey === oauthClient.providerKey503 && item.providerKey === oauthClient.providerKey
501 );504 );
@@ -506,11 +509,11 @@ const createExecutorStateStore = ( · 4 unmodified lines
506 }509 }
507 });510 });
508 }),511 }),
509 removeByWorkspaceAndSourceId: ({ workspaceId, sourceId }) =>512 removeByScopeAndSourceId: ({ scopeId, sourceId }) =>
510 effectTry("remove source oauth clients", () =>513 effectTry("remove source oauth clients", () =>
511 updateCollection("sourceOauthClients", (items) => {514 updateCollection("sourceOauthClients", (items) => {
512 const remaining = items.filter((item) =>515 const remaining = items.filter((item) =>
513 !(item.workspaceId === workspaceId && item.sourceId === sourceId)516 !(item.scopeId === scopeId && item.sourceId === sourceId)
514 );517 );
515 const removed = items.length - remaining.length;518 const removed = items.length - remaining.length;
516 items.splice(0, items.length, ...remaining);519 items.splice(0, items.length, ...remaining);
@@ -518,22 +521,22 @@ const createExecutorStateStore = ( · 1 unmodified line
518 }),521 }),
519 ),522 ),
520 },523 },
521 workspaceOauthClients: {524 scopeOauthClients: {
522 listByWorkspaceAndProvider: ({ workspaceId, providerKey }) =>525 listByScopeAndProvider: ({ scopeId, providerKey }) =>
523 effectTry("list workspace oauth clients", () =>526 effectTry("list workspace oauth clients", () =>
524 readCollection("workspaceOauthClients").filter((item) =>527 readCollection("scopeOauthClients").filter((item) =>
525 item.workspaceId === workspaceId && item.providerKey === providerKey528 item.scopeId === scopeId && item.providerKey === providerKey
526 ),529 ),
527 ),530 ),
528 getById: (id) =>531 getById: (id) =>
529 effectTry("get workspace oauth client", () =>532 effectTry("get workspace oauth client", () =>
530 Option.fromNullable(533 Option.fromNullable(
531 readCollection("workspaceOauthClients").find((item) => item.id === id),534 readCollection("scopeOauthClients").find((item) => item.id === id),
532 ),535 ),
533 ),536 ),
534 upsert: (oauthClient) =>537 upsert: (oauthClient) =>
535 effectTry("upsert workspace oauth client", () => {538 effectTry("upsert workspace oauth client", () => {
536 updateCollection("workspaceOauthClients", (items) => {539 updateCollection("scopeOauthClients", (items) => {
537 const index = items.findIndex((item) => item.id === oauthClient.id);540 const index = items.findIndex((item) => item.id === oauthClient.id);
538 if (index >= 0) {541 if (index >= 0) {
539 items[index] = oauthClient;542 items[index] = oauthClient;
@@ -544,7 +547,7 @@ const createExecutorStateStore = ( · 4 unmodified lines
544 }),547 }),
545 removeById: (id) =>548 removeById: (id) =>
546 effectTry("remove workspace oauth client", () =>549 effectTry("remove workspace oauth client", () =>
547 updateCollection("workspaceOauthClients", (items) => {550 updateCollection("scopeOauthClients", (items) => {
548 const remaining = items.filter((item) => item.id !== id);551 const remaining = items.filter((item) => item.id !== id);
549 const removed = remaining.length !== items.length;552 const removed = remaining.length !== items.length;
550 items.splice(0, items.length, ...remaining);553 items.splice(0, items.length, ...remaining);
@@ -553,15 +556,15 @@ const createExecutorStateStore = ( · 2 unmodified lines
553 ),556 ),
554 },557 },
555 providerAuthGrants: {558 providerAuthGrants: {
556 listByWorkspaceId: (workspaceId) =>559 listByScopeId: (scopeId) =>
557 effectTry("list provider grants by workspace", () =>560 effectTry("list provider grants by workspace", () =>
558 readCollection("providerAuthGrants").filter((item) => item.workspaceId === workspaceId),561 readCollection("providerAuthGrants").filter((item) => item.scopeId === scopeId),
559 ),562 ),
560 listByWorkspaceActorAndProvider: ({ workspaceId, actorAccountId, providerKey }) =>563 listByScopeActorAndProvider: ({ scopeId, actorScopeId, providerKey }) =>
561 effectTry("list provider grants by actor", () =>564 effectTry("list provider grants by actor", () =>
562 readCollection("providerAuthGrants").filter((item) =>565 readCollection("providerAuthGrants").filter((item) =>
563 item.workspaceId === workspaceId566 item.scopeId === scopeId
564 && item.actorAccountId === actorAccountId567 && item.actorScopeId === actorScopeId
565 && item.providerKey === providerKey568 && item.providerKey === providerKey
566 ),569 ),
567 ),570 ),
@@ -595,9 +598,9 @@ const createExecutorStateStore = ( · 27 unmodified lines
595 sourceAuthSessions: {598 sourceAuthSessions: {
596 listAll: () =>599 listAll: () =>
597 effectTry("list source auth sessions", () => readCollection("sourceAuthSessions")),600 effectTry("list source auth sessions", () => readCollection("sourceAuthSessions")),
598 listByWorkspaceId: (workspaceId) =>601 listByScopeId: (scopeId) =>
599 effectTry("list source auth sessions by workspace", () =>602 effectTry("list source auth sessions by workspace", () =>
600 readCollection("sourceAuthSessions").filter((item) => item.workspaceId === workspaceId),603 readCollection("sourceAuthSessions").filter((item) => item.scopeId === scopeId),
601 ),604 ),
602 getById: (id) =>605 getById: (id) =>
603 effectTry("get source auth session", () =>606 effectTry("get source auth session", () =>
@@ -611,13 +614,13 @@ const createExecutorStateStore = ( · 7 unmodified lines
611 readCollection("sourceAuthSessions").find((item) => item.state === state),614 readCollection("sourceAuthSessions").find((item) => item.state === state),
612 ),615 ),
613 ),616 ),
614 getPendingByWorkspaceSourceAndActor: ({ workspaceId, sourceId, actorAccountId, credentialSlot }) =>617 getPendingByScopeSourceAndActor: ({ scopeId, sourceId, actorScopeId, credentialSlot }) =>
615 effectTry("get pending source auth session", () =>618 effectTry("get pending source auth session", () =>
616 Option.fromNullable(619 Option.fromNullable(
617 readCollection("sourceAuthSessions").find((item) =>620 readCollection("sourceAuthSessions").find((item) =>
618 item.workspaceId === workspaceId621 item.scopeId === scopeId
619 && item.sourceId === sourceId622 && item.sourceId === sourceId
620 && item.actorAccountId === actorAccountId623 && item.actorScopeId === actorScopeId
621 && item.status === "pending"624 && item.status === "pending"
622 && (credentialSlot === undefined || item.credentialSlot === credentialSlot)625 && (credentialSlot === undefined || item.credentialSlot === credentialSlot)
623 ),626 ),
@@ -656,11 +659,11 @@ const createExecutorStateStore = ( · 32 unmodified lines
656 }659 }
657 });660 });
658 }),661 }),
659 removeByWorkspaceAndSourceId: (workspaceId, sourceId) =>662 removeByScopeAndSourceId: (scopeId, sourceId) =>
660 effectTry("remove source auth sessions", () =>663 effectTry("remove source auth sessions", () =>
661 updateCollection("sourceAuthSessions", (items) => {664 updateCollection("sourceAuthSessions", (items) => {
662 const remaining = items.filter((item) =>665 const remaining = items.filter((item) =>
663 !(item.workspaceId === workspaceId && item.sourceId === sourceId)666 !(item.scopeId === scopeId && item.sourceId === sourceId)
664 );667 );
665 const removed = remaining.length !== items.length;668 const removed = remaining.length !== items.length;
666 items.splice(0, items.length, ...remaining);669 items.splice(0, items.length, ...remaining);
@@ -739,11 +742,11 @@ const createExecutorStateStore = ( · 72 unmodified lines
739 readCollection("executions").find((item) => item.id === executionId),742 readCollection("executions").find((item) => item.id === executionId),
740 ),743 ),
741 ),744 ),
742 getByWorkspaceAndId: (workspaceId, executionId) =>745 getByScopeAndId: (scopeId, executionId) =>
743 effectTry("get execution by workspace and id", () =>746 effectTry("get execution by scope and id", () =>
744 Option.fromNullable(747 Option.fromNullable(
745 readCollection("executions").find((item) =>748 readCollection("executions").find((item) =>
746 item.workspaceId === workspaceId && item.id === executionId749 item.scopeId === scopeId && item.id === executionId
747 ),750 ),
748 ),751 ),
749 ),752 ),
@@ -932,9 +935,9 @@ export const createSqliteExecutorBackend = ( · 182 unmodified lines
932 const secretMaterial = createSecretMaterialBackend(executorState);935 const secretMaterial = createSecretMaterialBackend(executorState);
933 936
934 return {937 return {
935 workspace: {938 scope: {
936 workspaceName: options.workspaceName ?? "SQLite SDK Example",939 scopeName: options.scopeName ?? "SQLite SDK Example",
937 workspaceRoot: options.workspaceRoot ?? null,940 scopeRoot: options.scopeRoot ?? null,
938 metadata: {941 metadata: {
939 kind: "sqlite",942 kind: "sqlite",
940 databasePath,943 databasePath,
@@ -942,8 +945,8 @@ export const createSqliteExecutorBackend = ( · 1 unmodified line
942 },945 },
943 storage: {946 storage: {
944 installation: createInstallationStore(store, options),947 installation: createInstallationStore(store, options),
945 workspaceConfig: createWorkspaceConfigStore(store),948 scopeConfig: createScopeConfigStore(store),
946 workspaceState: createWorkspaceStateStore(store),949 scopeState: createScopeStateStore(store),
947 sourceArtifacts: createSourceArtifactStore(store),950 sourceArtifacts: createSourceArtifactStore(store),
948 executorState,951 executorState,
949 secretMaterial,952 secretMaterial,
packages/clients/react/package.json+1−0
@@ -24,6 +24,7 @@ · 23 unmodified lines
24 "@effect/platform": "catalog:",24 "@effect/platform": "catalog:",
25 "@effect/platform-node": "catalog:",25 "@effect/platform-node": "catalog:",
26 "@effect/vitest": "catalog:",26 "@effect/vitest": "catalog:",
27 "@executor/effect-test-utils": "workspace:*",
27 "@executor/platform-sdk-file": "workspace:*",28 "@executor/platform-sdk-file": "workspace:*",
28 "@types/jsdom": "^21.1.7",29 "@types/jsdom": "^21.1.7",
29 "@types/node": "catalog:",30 "@types/node": "catalog:",
packages/clients/react/src/index.test.tsx+46−107
@@ -1,8 +1,19 @@
1import { createServer } from "node:http";1import { createServer } from "node:http";
2 2
3import { FileSystem, HttpApiBuilder, HttpServer } from "@effect/platform";3import {
4 FileSystem,
5 HttpApi,
6 HttpApiBuilder,
7 HttpApiEndpoint,
8 HttpApiGroup,
9 HttpServer,
10 OpenApi,
11} from "@effect/platform";
4import { NodeFileSystem, NodeHttpServer } from "@effect/platform-node";12import { NodeFileSystem, NodeHttpServer } from "@effect/platform-node";
5import { describe, expect, it } from "@effect/vitest";13import { describe, expect, it } from "@effect/vitest";
14import {
15 startOpenApiTestServer,
16} from "@executor/effect-test-utils";
6import {17import {
7 createExecutorApiLayer,18 createExecutorApiLayer,
8} from "@executor/platform-api";19} from "@executor/platform-api";
@@ -17,6 +28,7 @@ import * as Context from "effect/Context"; · 8 unmodified lines
17import * as Effect from "effect/Effect";28import * as Effect from "effect/Effect";
18import * as Exit from "effect/Exit";29import * as Exit from "effect/Exit";
19import * as Layer from "effect/Layer";30import * as Layer from "effect/Layer";
31import * as Schema from "effect/Schema";
20import * as Scope from "effect/Scope";32import * as Scope from "effect/Scope";
21import { JSDOM } from "jsdom";33import { JSDOM } from "jsdom";
22import * as React from "react";34import * as React from "react";
@@ -225,83 +237,38 @@ type OpenApiSpecServer = RunningServer & { · 202 unmodified lines
225 specUrl: string;237 specUrl: string;
226};238};
227 239
228const startOpenApiSpecServer = async (): Promise<OpenApiSpecServer> => {240class HooksTestPingApi extends HttpApiGroup.make("ping")
229 const spec = {241 .add(
230 openapi: "3.0.3",242 HttpApiEndpoint.get("ping")`/ping`
231 info: {243 .addSuccess(
232 title: "Hook Test API",244 Schema.Struct({
233 version: "1.0.0",245 ok: Schema.Boolean,
234 },246 }),
235 paths: {247 ),
236 "/ping": {248 )
237 get: {249{}
238 operationId: "ping",250
239 responses: {251class HooksTestOpenApi extends HttpApi.make("hooksTest").add(HooksTestPingApi) {}
240 200: {252
241 description: "ok",253const hooksTestOpenApiSpec = OpenApi.fromApi(HooksTestOpenApi);
242 content: {254const hooksTestOpenApiLive = HttpApiBuilder.group(
243 "application/json": {255 HooksTestOpenApi,
244 schema: {256 "ping",
245 type: "object",257 (handlers) =>
246 properties: {258 handlers.handle("ping", () =>
247 ok: { type: "boolean" },259 Effect.succeed({
248 },260 ok: true,
249 },261 }),
250 },262 ),
251 },263);
252 },264const hooksTestOpenApiLayer = HttpApiBuilder.api(HooksTestOpenApi).pipe(
253 },265 Layer.provide(hooksTestOpenApiLive),
254 },266);
255 },
256 },
257 };
258
259 const server = createServer((req, res) => {
260 if (req.url === "/openapi.json") {
261 res.statusCode = 200;
262 res.setHeader("content-type", "application/json");
263 res.end(JSON.stringify(spec));
264 return;
265 }
266
267 if (req.url === "/ping") {
268 res.statusCode = 200;
269 res.setHeader("content-type", "application/json");
270 res.end(JSON.stringify({ ok: true }));
271 return;
272 }
273
274 res.statusCode = 404;
275 res.end("not found");
276 });
277 267
278 await new Promise<void>((resolve, reject) => {268const startOpenApiSpecServer = async (): Promise<OpenApiSpecServer> => {
279 server.once("error", reject);269 return startOpenApiTestServer({
280 server.listen(0, "127.0.0.1", () => resolve());270 apiLayer: hooksTestOpenApiLayer,
281 });271 });
282
283 const address = server.address();
284 if (!address || typeof address === "string") {
285 throw new Error("Failed to bind OpenAPI spec server");
286 }
287
288 const baseUrl = `http://127.0.0.1:${address.port}`;
289
290 return {
291 baseUrl,
292 specUrl: `${baseUrl}/openapi.json`,
293 close: () =>
294 new Promise<void>((resolve, reject) => {
295 server.close((error) => {
296 if (error) {
297 reject(error);
298 return;
299 }
300
301 resolve();
302 });
303 }),
304 };
305};272};
306 273
307async function requestJson<T>(input: {274async function requestJson<T>(input: {
@@ -343,42 +310,14 @@ const seedStoredOpenApiSource = async (input: { · 35 unmodified lines
343 name: string;310 name: string;
344 specUrl?: string;311 specUrl?: string;
345}): Promise<Source> => {312}): Promise<Source> => {
346 const sourceDocumentText = JSON.stringify({313 const sourceDocumentText = JSON.stringify(hooksTestOpenApiSpec);
347 openapi: "3.0.3",
348 info: {
349 title: input.name,
350 version: "1.0.0",
351 },
352 paths: {
353 "/ping": {
354 get: {
355 operationId: "ping",
356 responses: {
357 200: {
358 description: "ok",
359 content: {
360 "application/json": {
361 schema: {
362 type: "object",
363 properties: {
364 ok: { type: "boolean" },
365 },
366 },
367 },
368 },
369 },
370 },
371 },
372 },
373 },
374 });
375 const specUrl = input.specUrl ?? `data:application/json,${encodeURIComponent(sourceDocumentText)}`;314 const specUrl = input.specUrl ?? `data:application/json,${encodeURIComponent(sourceDocumentText)}`;
376 315
377 return requestJson<Source>({316 return requestJson<Source>({
378 baseUrl: input.server.baseUrl,317 baseUrl: input.server.baseUrl,
379 path: `/v1/workspaces/${input.installation.workspaceId}/sources`,318 path: `/v1/workspaces/${input.installation.scopeId}/sources`,
380 method: "POST",319 method: "POST",
381 accountId: input.installation.accountId,320 accountId: input.installation.actorScopeId,
382 payload: {321 payload: {
383 name: input.name,322 name: input.name,
384 kind: "openapi",323 kind: "openapi",
packages/clients/react/src/index.ts+21−21
@@ -32,7 +32,7 @@ import type { · 31 unmodified lines
32 SourceInspection,32 SourceInspection,
33 SourceInspectionDiscoverResult,33 SourceInspectionDiscoverResult,
34 SourceInspectionToolDetail,34 SourceInspectionToolDetail,
35 WorkspaceOauthClient,35 ScopeOauthClient as WorkspaceOauthClient,
36} from "@executor/platform-sdk/schema";36} from "@executor/platform-sdk/schema";
37import * as Cause from "effect/Cause";37import * as Cause from "effect/Cause";
38import * as Effect from "effect/Effect";38import * as Effect from "effect/Effect";
@@ -42,7 +42,7 @@ import * as Runtime from "effect/Runtime"; · 3 unmodified lines
42import * as React from "react";42import * as React from "react";
43 43
44const DEFAULT_EXECUTOR_API_BASE_URL = "http://127.0.0.1:8788";44const DEFAULT_EXECUTOR_API_BASE_URL = "http://127.0.0.1:8788";
45const PLACEHOLDER_WORKSPACE_ID = "ws_placeholder" as Source["workspaceId"];45const PLACEHOLDER_WORKSPACE_ID = "ws_placeholder" as Source["scopeId"];
46const PLACEHOLDER_ACCOUNT_ID = "acc_placeholder";46const PLACEHOLDER_ACCOUNT_ID = "acc_placeholder";
47const PLACEHOLDER_SOURCE_ID = "src_placeholder" as Source["id"];47const PLACEHOLDER_SOURCE_ID = "src_placeholder" as Source["id"];
48 48
@@ -58,18 +58,18 @@ export type SourceRemoveResult = { · 9 unmodified lines
58 58
59type AtomKeyPart = string | number | boolean | null | undefined;59type AtomKeyPart = string | number | boolean | null | undefined;
60 60
61type SourcesKeyParts = readonly [boolean, Source["workspaceId"], string];61type SourcesKeyParts = readonly [boolean, Source["scopeId"], string];
62type SourceKeyParts = readonly [boolean, Source["workspaceId"], string, Source["id"]];62type SourceKeyParts = readonly [boolean, Source["scopeId"], string, Source["id"]];
63type SourceToolDetailKeyParts = readonly [63type SourceToolDetailKeyParts = readonly [
64 boolean,64 boolean,
65 Source["workspaceId"],65 Source["scopeId"],
66 string,66 string,
67 Source["id"],67 Source["id"],
68 string | null,68 string | null,
69];69];
70type SourceDiscoveryKeyParts = readonly [70type SourceDiscoveryKeyParts = readonly [
71 boolean,71 boolean,
72 Source["workspaceId"],72 Source["scopeId"],
73 string,73 string,
74 Source["id"],74 Source["id"],
75 string,75 string,
@@ -77,13 +77,13 @@ type SourceDiscoveryKeyParts = readonly [ · 1 unmodified line
77];77];
78type WorkspaceOauthClientsKeyParts = readonly [78type WorkspaceOauthClientsKeyParts = readonly [
79 boolean,79 boolean,
80 Source["workspaceId"],80 Source["scopeId"],
81 string,81 string,
82 string,82 string,
83];83];
84 84
85type InvalidationTarget = {85type InvalidationTarget = {
86 workspaceId?: Source["workspaceId"];86 workspaceId?: Source["scopeId"];
87 accountId?: string;87 accountId?: string;
88 sourceId?: Source["id"];88 sourceId?: Source["id"];
89};89};
@@ -104,7 +104,7 @@ type ExecutorQueryContextValue = { · 14 unmodified lines
104};104};
105 105
106type MutationExecutionContext = {106type MutationExecutionContext = {
107 workspaceId: Source["workspaceId"];107 workspaceId: Source["scopeId"];
108 accountId: string;108 accountId: string;
109 registry: Registry.Registry;109 registry: Registry.Registry;
110 invalidateQueries: (target?: InvalidationTarget) => void;110 invalidateQueries: (target?: InvalidationTarget) => void;
@@ -149,20 +149,20 @@ const decodeAtomKey = <T extends ReadonlyArray<AtomKeyPart>>(key: string): T => · 38 unmodified lines
149 149
150const encodeSourcesKey = (150const encodeSourcesKey = (
151 enabled: boolean,151 enabled: boolean,
152 workspaceId: Source["workspaceId"],152 workspaceId: Source["scopeId"],
153 accountId: string,153 accountId: string,
154): string => encodeAtomKey([enabled, workspaceId, accountId] satisfies SourcesKeyParts);154): string => encodeAtomKey([enabled, workspaceId, accountId] satisfies SourcesKeyParts);
155 155
156const encodeSourceKey = (156const encodeSourceKey = (
157 enabled: boolean,157 enabled: boolean,
158 workspaceId: Source["workspaceId"],158 workspaceId: Source["scopeId"],
159 accountId: string,159 accountId: string,
160 sourceId: Source["id"],160 sourceId: Source["id"],
161): string => encodeAtomKey([enabled, workspaceId, accountId, sourceId] satisfies SourceKeyParts);161): string => encodeAtomKey([enabled, workspaceId, accountId, sourceId] satisfies SourceKeyParts);
162 162
163const encodeToolDetailKey = (163const encodeToolDetailKey = (
164 enabled: boolean,164 enabled: boolean,
165 workspaceId: Source["workspaceId"],165 workspaceId: Source["scopeId"],
166 accountId: string,166 accountId: string,
167 sourceId: Source["id"],167 sourceId: Source["id"],
168 toolPath: string | null,168 toolPath: string | null,
@@ -171,7 +171,7 @@ const encodeToolDetailKey = ( · 2 unmodified lines
171 171
172const encodeDiscoveryKey = (172const encodeDiscoveryKey = (
173 enabled: boolean,173 enabled: boolean,
174 workspaceId: Source["workspaceId"],174 workspaceId: Source["scopeId"],
175 accountId: string,175 accountId: string,
176 sourceId: Source["id"],176 sourceId: Source["id"],
177 query: string,177 query: string,
@@ -181,7 +181,7 @@ const encodeDiscoveryKey = ( · 3 unmodified lines
181 181
182const encodeWorkspaceOauthClientsKey = (182const encodeWorkspaceOauthClientsKey = (
183 enabled: boolean,183 enabled: boolean,
184 workspaceId: Source["workspaceId"],184 workspaceId: Source["scopeId"],
185 accountId: string,185 accountId: string,
186 providerKey: string,186 providerKey: string,
187): string =>187): string =>
@@ -430,7 +430,7 @@ export type Loadable<T> = · 242 unmodified lines
430 430
431type WorkspaceContext = {431type WorkspaceContext = {
432 installation: LocalInstallation;432 installation: LocalInstallation;
433 workspaceId: Source["workspaceId"];433 workspaceId: Source["scopeId"];
434 accountId: string;434 accountId: string;
435};435};
436 436
@@ -483,8 +483,8 @@ const useWorkspaceContext = (): Loadable<WorkspaceContext> => { · 46 unmodified lines
483 status: "ready",483 status: "ready",
484 data: {484 data: {
485 installation: installation.data,485 installation: installation.data,
486 workspaceId: installation.data.workspaceId,486 workspaceId: installation.data.scopeId,
487 accountId: installation.data.accountId,487 accountId: installation.data.actorScopeId,
488 },488 },
489 } satisfies Loadable<WorkspaceContext>;489 } satisfies Loadable<WorkspaceContext>;
490 }, [installation]);490 }, [installation]);
@@ -559,7 +559,7 @@ const createActiveQueryCollections = (): ActiveQueryCollections => ({ · 68 unmodified lines
559 559
560const targetMatches = (560const targetMatches = (
561 target: InvalidationTarget | undefined,561 target: InvalidationTarget | undefined,
562 workspaceId: Source["workspaceId"],562 workspaceId: Source["scopeId"],
563 accountId: string,563 accountId: string,
564 sourceId?: Source["id"],564 sourceId?: Source["id"],
565): boolean => {565): boolean => {
@@ -671,14 +671,14 @@ const removeSourceFromList = ( · 105 unmodified lines
671): ReadonlyArray<Source> => sources.filter((source) => source.id !== sourceId);671): ReadonlyArray<Source> => sources.filter((source) => source.id !== sourceId);
672 672
673const createOptimisticSource = (input: {673const createOptimisticSource = (input: {
674 workspaceId: Source["workspaceId"];674 workspaceId: Source["scopeId"];
675 payload: CreateSourcePayload;675 payload: CreateSourcePayload;
676}): Source => {676}): Source => {
677 const now = Date.now();677 const now = Date.now();
678 678
679 return {679 return {
680 id: `src_optimistic_${crypto.randomUUID()}` as Source["id"],680 id: `src_optimistic_${crypto.randomUUID()}` as Source["id"],
681 workspaceId: input.workspaceId,681 scopeId: input.workspaceId,
682 name: input.payload.name,682 name: input.payload.name,
683 kind: input.payload.kind,683 kind: input.payload.kind,
684 endpoint: input.payload.endpoint,684 endpoint: input.payload.endpoint,
@@ -713,7 +713,7 @@ const applyUpdatePayloadToSource = (source: Source, payload: UpdateSourcePayload · 28 unmodified lines
713 713
714const useSourceMutation = <TInput, TOutput, TOptimistic = never>(714const useSourceMutation = <TInput, TOutput, TOptimistic = never>(
715 execute: (input: {715 execute: (input: {
716 workspaceId: Source["workspaceId"];716 workspaceId: Source["scopeId"];
717 accountId: string;717 accountId: string;
718 payload: TInput;718 payload: TInput;
719 }) => Promise<TOutput>,719 }) => Promise<TOutput>,
packages/dev/effect-test-utils/package.json+22−0
@@ -0,0 +1,22 @@
1{
2 "name": "@executor/effect-test-utils",
3 "private": true,
4 "type": "module",
5 "exports": {
6 ".": "./src/index.ts"
7 },
8 "scripts": {
9 "typecheck": "bunx tsc --noEmit -p tsconfig.json"
10 },
11 "dependencies": {
12 "@effect/platform": "catalog:",
13 "@effect/platform-node": "catalog:",
14 "effect": "catalog:"
15 },
16 "devDependencies": {
17 "@types/node": "catalog:",
18 "bun-types": "catalog:",
19 "typescript": "^5.9.3"
20 },
21 "version": "0.0.0"
22}
packages/dev/effect-test-utils/src/index.ts+103−0
@@ -0,0 +1,103 @@
1import { createServer } from "node:http";
2
3import { HttpApiBuilder, HttpServer } from "@effect/platform";
4import { NodeHttpServer } from "@effect/platform-node";
5import * as Context from "effect/Context";
6import * as Effect from "effect/Effect";
7import * as Exit from "effect/Exit";
8import * as Layer from "effect/Layer";
9import * as Scope from "effect/Scope";
10
11export type RunningHttpApiTestServer = {
12 baseUrl: string;
13 close: () => Promise<void>;
14};
15
16export type RunningOpenApiTestServer = RunningHttpApiTestServer & {
17 specUrl: string;
18};
19
20type TestApiLayer = Layer.Layer<any, any, never>;
21
22type HttpApiTestServerOptions = {
23 apiLayer: TestApiLayer;
24 host?: string;
25 openApiPath?: `/${string}`;
26};
27
28const createHttpApiServerLayer = (options: HttpApiTestServerOptions) => {
29 const host = options.host ?? "127.0.0.1";
30 const openApiPath = options.openApiPath;
31 const apiLayerWithOpenApi =
32 openApiPath === undefined
33 ? options.apiLayer
34 : Layer.mergeAll(
35 options.apiLayer,
36 HttpApiBuilder.middlewareOpenApi({ path: openApiPath }).pipe(
37 Layer.provideMerge(options.apiLayer),
38 ),
39 );
40
41 return HttpApiBuilder.serve().pipe(
42 Layer.provide(apiLayerWithOpenApi),
43 Layer.provideMerge(
44 NodeHttpServer.layer(() => createServer(), {
45 port: 0,
46 host,
47 }),
48 ),
49 );
50};
51
52export const startHttpApiTestServer = async (
53 options: HttpApiTestServerOptions,
54): Promise<RunningHttpApiTestServer> => {
55 const scope = await Effect.runPromise(Scope.make());
56
57 try {
58 const context = await Effect.runPromise(
59 Layer.buildWithScope(createHttpApiServerLayer(options), scope),
60 );
61 const server = Context.get(context, HttpServer.HttpServer);
62 const baseUrl = HttpServer.formatAddress(server.address);
63
64 return {
65 baseUrl,
66 close: () => Effect.runPromise(Scope.close(scope, Exit.void)),
67 };
68 } catch (error) {
69 await Effect.runPromise(Scope.close(scope, Exit.void));
70 throw error;
71 }
72};
73
74export const startOpenApiTestServer = async (
75 options: HttpApiTestServerOptions,
76): Promise<RunningOpenApiTestServer> => {
77 const openApiPath = options.openApiPath ?? "/openapi.json";
78 const server = await startHttpApiTestServer({
79 ...options,
80 openApiPath,
81 });
82
83 return {
84 ...server,
85 specUrl: `${server.baseUrl}${openApiPath}`,
86 };
87};
88
89export const makeHttpApiTestServer = (
90 options: HttpApiTestServerOptions,
91): Effect.Effect<RunningHttpApiTestServer, never, Scope.Scope> =>
92 Effect.acquireRelease(
93 Effect.promise(() => startHttpApiTestServer(options)),
94 (server) => Effect.promise(() => server.close()).pipe(Effect.orDie),
95 );
96
97export const makeOpenApiTestServer = (
98 options: HttpApiTestServerOptions,
99): Effect.Effect<RunningOpenApiTestServer, never, Scope.Scope> =>
100 Effect.acquireRelease(
101 Effect.promise(() => startOpenApiTestServer(options)),
102 (server) => Effect.promise(() => server.close()).pipe(Effect.orDie),
103 );
packages/dev/effect-test-utils/tsconfig.json+15−0
@@ -0,0 +1,15 @@
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": [
13 "src/**/*.ts"
14 ]
15}
packages/hosts/mcp/src/index.ts+24−24
@@ -154,8 +154,8 @@ const loadExecuteDescription = (runtime: ExecutorRuntime): Promise<string> => · 153 unmodified lines
154 Effect.gen(function* () {154 Effect.gen(function* () {
155 const resolveExecutionEnvironment = yield* RuntimeExecutionResolverService;155 const resolveExecutionEnvironment = yield* RuntimeExecutionResolverService;
156 const environment = yield* resolveExecutionEnvironment({156 const environment = yield* resolveExecutionEnvironment({
157 workspaceId: runtime.localInstallation.workspaceId,157 scopeId: runtime.localInstallation.scopeId,
158 accountId: runtime.localInstallation.accountId,158 actorScopeId: runtime.localInstallation.actorScopeId,
159 executionId: ExecutionIdSchema.make("exec_mcp_help"),159 executionId: ExecutionIdSchema.make("exec_mcp_help"),
160 });160 });
161 161
@@ -260,7 +260,7 @@ const buildToolResult = (envelope: ExecutionEnvelope): ExecutorMcpToolResult => · 98 unmodified lines
260 260
261const waitForInteractionProgress = async (input: {261const waitForInteractionProgress = async (input: {
262 runtime: ExecutorRuntime;262 runtime: ExecutorRuntime;
263 workspaceId: string;263 scopeId: string;
264 executionId: string;264 executionId: string;
265 pendingInteractionId: string;265 pendingInteractionId: string;
266}): Promise<ExecutionEnvelope> => {266}): Promise<ExecutionEnvelope> => {
@@ -268,7 +268,7 @@ const waitForInteractionProgress = async (input: { · 1 unmodified line
268 const next = await runControlPlane(268 const next = await runControlPlane(
269 input.runtime,269 input.runtime,
270 getExecution({270 getExecution({
271 workspaceId: input.workspaceId as never,271 scopeId: input.scopeId as never,
272 executionId: input.executionId as never,272 executionId: input.executionId as never,
273 }),273 }),
274 );274 );
@@ -286,8 +286,8 @@ const waitForInteractionProgress = async (input: { · 11 unmodified lines
286 286
287const driveExecutionWithElicitation = async (input: {287const driveExecutionWithElicitation = async (input: {
288 runtime: ExecutorRuntime;288 runtime: ExecutorRuntime;
289 workspaceId: string;289 scopeId: string;
290 accountId: string;290 actorScopeId: string;
291 server: McpServer;291 server: McpServer;
292 envelope: ExecutionEnvelope;292 envelope: ExecutionEnvelope;
293}): Promise<ExecutionEnvelope> => {293}): Promise<ExecutionEnvelope> => {
@@ -317,13 +317,13 @@ const driveExecutionWithElicitation = async (input: { · 23 unmodified lines
317 current = await runControlPlane(317 current = await runControlPlane(
318 input.runtime,318 input.runtime,
319 resumeExecution({319 resumeExecution({
320 workspaceId: input.workspaceId as never,320 scopeId: input.scopeId as never,
321 executionId: current.execution.id as never,321 executionId: current.execution.id as never,
322 payload: {322 payload: {
323 responseJson: JSON.stringify(response),323 responseJson: JSON.stringify(response),
324 interactionMode: interactionModeForServer(input.server),324 interactionMode: interactionModeForServer(input.server),
325 },325 },
326 resumedByAccountId: input.accountId as never,326 resumedByScopeId: input.actorScopeId as never,
327 }),327 }),
328 );328 );
329 continue;329 continue;
@@ -340,13 +340,13 @@ const driveExecutionWithElicitation = async (input: { · 10 unmodified lines
340 current = await runControlPlane(340 current = await runControlPlane(
341 input.runtime,341 input.runtime,
342 resumeExecution({342 resumeExecution({
343 workspaceId: input.workspaceId as never,343 scopeId: input.scopeId as never,
344 executionId: current.execution.id as never,344 executionId: current.execution.id as never,
345 payload: {345 payload: {
346 responseJson: JSON.stringify(response),346 responseJson: JSON.stringify(response),
347 interactionMode: interactionModeForServer(input.server),347 interactionMode: interactionModeForServer(input.server),
348 },348 },
349 resumedByAccountId: input.accountId as never,349 resumedByScopeId: input.actorScopeId as never,
350 }),350 }),
351 );351 );
352 continue;352 continue;
@@ -354,7 +354,7 @@ const driveExecutionWithElicitation = async (input: { · 1 unmodified line
354 354
355 current = await waitForInteractionProgress({355 current = await waitForInteractionProgress({
356 runtime: input.runtime,356 runtime: input.runtime,
357 workspaceId: input.workspaceId,357 scopeId: input.scopeId,
358 executionId: current.execution.id,358 executionId: current.execution.id,
359 pendingInteractionId: pending.id,359 pendingInteractionId: pending.id,
360 });360 });
@@ -365,15 +365,15 @@ const driveExecutionWithElicitation = async (input: { · 4 unmodified lines
365 365
366const driveExecutionWithoutElicitation = async (input: {366const driveExecutionWithoutElicitation = async (input: {
367 runtime: ExecutorRuntime;367 runtime: ExecutorRuntime;
368 workspaceId: string;368 scopeId: string;
369 accountId: string;369 actorScopeId: string;
370 executionId: string;370 executionId: string;
371 initialResponse?: ResumeResponseInput;371 initialResponse?: ResumeResponseInput;
372}): Promise<ExecutionEnvelope> => {372}): Promise<ExecutionEnvelope> => {
373 let current = await runControlPlane(373 let current = await runControlPlane(
374 input.runtime,374 input.runtime,
375 getExecution({375 getExecution({
376 workspaceId: input.workspaceId as never,376 scopeId: input.scopeId as never,
377 executionId: input.executionId as never,377 executionId: input.executionId as never,
378 }),378 }),
379 );379 );
@@ -388,13 +388,13 @@ const driveExecutionWithoutElicitation = async (input: { · 8 unmodified lines
388 current = await runControlPlane(388 current = await runControlPlane(
389 input.runtime,389 input.runtime,
390 resumeExecution({390 resumeExecution({
391 workspaceId: input.workspaceId as never,391 scopeId: input.scopeId as never,
392 executionId: current.execution.id as never,392 executionId: current.execution.id as never,
393 payload: {393 payload: {
394 responseJson: JSON.stringify(response),394 responseJson: JSON.stringify(response),
395 interactionMode: "detach",395 interactionMode: "detach",
396 },396 },
397 resumedByAccountId: input.accountId as never,397 resumedByScopeId: input.actorScopeId as never,
398 }),398 }),
399 );399 );
400 response = undefined;400 response = undefined;
@@ -416,8 +416,8 @@ const createExecutorMcpServer = async (config: { · 15 unmodified lines
416 },416 },
417 );417 );
418 418
419 const workspaceId = config.runtime.localInstallation.workspaceId;419 const scopeId = config.runtime.localInstallation.scopeId;
420 const accountId = config.runtime.localInstallation.accountId;420 const actorScopeId = config.runtime.localInstallation.actorScopeId;
421 421
422 const executeTool = server.registerTool(422 const executeTool = server.registerTool(
423 "execute",423 "execute",
@@ -429,20 +429,20 @@ const createExecutorMcpServer = async (config: { · 5 unmodified lines
429 let created = await runControlPlane(429 let created = await runControlPlane(
430 config.runtime,430 config.runtime,
431 createExecution({431 createExecution({
432 workspaceId,432 scopeId,
433 payload: {433 payload: {
434 code,434 code,
435 interactionMode: interactionModeForServer(server),435 interactionMode: interactionModeForServer(server),
436 },436 },
437 createdByAccountId: accountId,437 createdByScopeId: actorScopeId,
438 }),438 }),
439 );439 );
440 440
441 if (supportsManagedElicitation(server)) {441 if (supportsManagedElicitation(server)) {
442 created = await driveExecutionWithElicitation({442 created = await driveExecutionWithElicitation({
443 runtime: config.runtime,443 runtime: config.runtime,
444 workspaceId,444 scopeId,
445 accountId,445 actorScopeId,
446 server,446 server,
447 envelope: created,447 envelope: created,
448 });448 });
@@ -469,8 +469,8 @@ const createExecutorMcpServer = async (config: { · 20 unmodified lines
469 ) => {469 ) => {
470 const resumed = await driveExecutionWithoutElicitation({470 const resumed = await driveExecutionWithoutElicitation({
471 runtime: config.runtime,471 runtime: config.runtime,
472 workspaceId,472 scopeId,
473 accountId,473 actorScopeId,
474 executionId: input.resumePayload.executionId,474 executionId: input.resumePayload.executionId,
475 initialResponse: input.response,475 initialResponse: input.response,
476 });476 });
packages/hosts/mcp/src/paused-result.test.ts+2−2
@@ -35,8 +35,8 @@ const makeEnvelope = ( · 34 unmodified lines
35): ExecutionEnvelope => ({35): ExecutionEnvelope => ({
36 execution: {36 execution: {
37 id: "exec_123" as never,37 id: "exec_123" as never,
38 workspaceId: "ws_123" as never,38 scopeId: "ws_123" as never,
39 createdByAccountId: "acct_123" as never,39 createdByScopeId: "acct_123" as never,
40 status: "waiting_for_interaction",40 status: "waiting_for_interaction",
41 code: "return await tools.executor.sources.add({ endpoint: 'https://mcp.axiom.co/mcp' });",41 code: "return await tools.executor.sources.add({ endpoint: 'https://mcp.axiom.co/mcp' });",
42 resultJson: null,42 resultJson: null,
packages/platform/api/src/executions/api.ts+1−1
@@ -10,7 +10,7 @@ export type { · 9 unmodified lines
10import {10import {
11 ExecutionIdSchema,11 ExecutionIdSchema,
12 ExecutionEnvelopeSchema,12 ExecutionEnvelopeSchema,
13 WorkspaceIdSchema,13 ScopeIdSchema as WorkspaceIdSchema,
14} from "@executor/platform-sdk/schema";14} from "@executor/platform-sdk/schema";
15 15
16import {16import {
packages/platform/api/src/executor-context.ts+3−3
@@ -1,5 +1,5 @@
1import type { Executor } from "@executor/platform-sdk";1import type { Executor } from "@executor/platform-sdk";
2import type { WorkspaceId } from "@executor/platform-sdk/schema";2import type { ScopeId as WorkspaceId } from "@executor/platform-sdk/schema";
3import {3import {
4 ControlPlaneBadRequestError,4 ControlPlaneBadRequestError,
5 ControlPlaneForbiddenError,5 ControlPlaneForbiddenError,
@@ -23,13 +23,13 @@ export const resolveRequestedLocalWorkspace = ( · 17 unmodified lines
23 workspaceId: WorkspaceId,23 workspaceId: WorkspaceId,
24) =>24) =>
25 Effect.flatMap(ControlPlaneExecutor, (executor) =>25 Effect.flatMap(ControlPlaneExecutor, (executor) =>
26 executor.workspaceId === workspaceId26 executor.scopeId === workspaceId
27 ? Effect.succeed(executor)27 ? Effect.succeed(executor)
28 : Effect.fail(28 : Effect.fail(
29 new ControlPlaneForbiddenError({29 new ControlPlaneForbiddenError({
30 operation,30 operation,
31 message: "Requested workspace is not the active local workspace",31 message: "Requested workspace is not the active local workspace",
32 details: `requestedWorkspaceId=${workspaceId} activeWorkspaceId=${executor.workspaceId}`,32 details: `requestedWorkspaceId=${workspaceId} activeWorkspaceId=${executor.scopeId}`,
33 }),33 }),
34 ),34 ),
35 );35 );
packages/platform/api/src/oauth/api.ts+1−1
@@ -4,7 +4,7 @@ import { · 3 unmodified lines
4 SourceAuthSessionIdSchema,4 SourceAuthSessionIdSchema,
5 SourceTransportSchema,5 SourceTransportSchema,
6 StringMapSchema,6 StringMapSchema,
7 WorkspaceIdSchema,7 ScopeIdSchema as WorkspaceIdSchema,
8} from "@executor/platform-sdk/schema";8} from "@executor/platform-sdk/schema";
9import * as Schema from "effect/Schema";9import * as Schema from "effect/Schema";
10 10
packages/platform/api/src/oauth/http.ts+1−1
@@ -3,7 +3,7 @@ import { · 2 unmodified lines
3 HttpServerRequest,3 HttpServerRequest,
4 HttpServerResponse,4 HttpServerResponse,
5} from "@effect/platform";5} from "@effect/platform";
6import type { WorkspaceId } from "@executor/platform-sdk/schema";6import type { ScopeId as WorkspaceId } from "@executor/platform-sdk/schema";
7import * as Cause from "effect/Cause";7import * as Cause from "effect/Cause";
8import * as Either from "effect/Either";8import * as Either from "effect/Either";
9import * as Effect from "effect/Effect";9import * as Effect from "effect/Effect";
packages/platform/api/src/policies/api.ts+2−2
@@ -11,9 +11,9 @@ import { · 10 unmodified lines
11 ControlPlaneUnauthorizedError,11 ControlPlaneUnauthorizedError,
12} from "@executor/platform-sdk/errors";12} from "@executor/platform-sdk/errors";
13import {13import {
14 LocalWorkspacePolicySchema,14 LocalScopePolicySchema as LocalWorkspacePolicySchema,
15 PolicyIdSchema,15 PolicyIdSchema,
16 WorkspaceIdSchema,16 ScopeIdSchema as WorkspaceIdSchema,
17} from "@executor/platform-sdk/schema";17} from "@executor/platform-sdk/schema";
18import * as Schema from "effect/Schema";18import * as Schema from "effect/Schema";
19 19
packages/platform/api/src/sources/api.ts+6−6
@@ -5,13 +5,13 @@ import { · 4 unmodified lines
5 ConnectSourcePayloadSchema,5 ConnectSourcePayloadSchema,
6 ConnectSourceResultSchema,6 ConnectSourceResultSchema,
7 CreateSourcePayloadSchema,7 CreateSourcePayloadSchema,
8 CreateWorkspaceOauthClientPayloadSchema,8 CreateScopeOauthClientPayloadSchema as CreateWorkspaceOauthClientPayloadSchema,
9 CredentialOauthCompleteUrlParamsSchema,9 CredentialOauthCompleteUrlParamsSchema,
10 CredentialPageUrlParamsSchema,10 CredentialPageUrlParamsSchema,
11 CredentialSubmitPayloadSchema,11 CredentialSubmitPayloadSchema,
12 DiscoverSourcePayloadSchema,12 DiscoverSourcePayloadSchema,
13 UpdateSourcePayloadSchema,13 UpdateSourcePayloadSchema,
14 WorkspaceOauthClientQuerySchema,14 ScopeOauthClientQuerySchema as WorkspaceOauthClientQuerySchema,
15} from "@executor/platform-sdk/contracts";15} from "@executor/platform-sdk/contracts";
16import {16import {
17 ControlPlaneBadRequestError,17 ControlPlaneBadRequestError,
@@ -29,9 +29,9 @@ import { · 11 unmodified lines
29 SourceInspectionSchema,29 SourceInspectionSchema,
30 SourceInspectionToolDetailSchema,30 SourceInspectionToolDetailSchema,
31 SourceSchema,31 SourceSchema,
32 WorkspaceOauthClientIdSchema,32 ScopeOauthClientIdSchema as WorkspaceOauthClientIdSchema,
33 WorkspaceOauthClientSchema,33 ScopeOauthClientSchema as WorkspaceOauthClientSchema,
34 WorkspaceIdSchema,34 ScopeIdSchema as WorkspaceIdSchema,
35} from "@executor/platform-sdk/schema";35} from "@executor/platform-sdk/schema";
36import * as Schema from "effect/Schema";36import * as Schema from "effect/Schema";
37 37
@@ -41,7 +41,7 @@ export type { · 3 unmodified lines
41 ConnectSourcePayload,41 ConnectSourcePayload,
42 ConnectSourceResult,42 ConnectSourceResult,
43 CreateSourcePayload,43 CreateSourcePayload,
44 CreateWorkspaceOauthClientPayload,44 CreateScopeOauthClientPayload as CreateWorkspaceOauthClientPayload,
45 DiscoverSourcePayload,45 DiscoverSourcePayload,
46 UpdateSourcePayload,46 UpdateSourcePayload,
47} from "@executor/platform-sdk/contracts";47} from "@executor/platform-sdk/contracts";
packages/platform/api/src/sources/http.ts+6−6
@@ -6,8 +6,8 @@ import { · 5 unmodified lines
6import type {6import type {
7 ExecutionInteraction,7 ExecutionInteraction,
8 Source,8 Source,
9 WorkspaceId,9 ScopeId as WorkspaceId,
10 WorkspaceOauthClient,10 ScopeOauthClient as WorkspaceOauthClient,
11} from "@executor/platform-sdk/schema";11} from "@executor/platform-sdk/schema";
12import type {12import type {
13 ExecutorMcpSourceInput,13 ExecutorMcpSourceInput,
@@ -18,7 +18,7 @@ import * as Effect from "effect/Effect"; · 4 unmodified lines
18import {18import {
19 sourceAdapterRequiresInteractiveConnect,19 sourceAdapterRequiresInteractiveConnect,
20} from "@executor/platform-sdk/runtime";20} from "@executor/platform-sdk/runtime";
21import { WorkspaceOauthClientIdSchema } from "@executor/platform-sdk/schema";21import { ScopeOauthClientIdSchema as WorkspaceOauthClientIdSchema } from "@executor/platform-sdk/schema";
22 22
23import {23import {
24 ControlPlaneBadRequestError,24 ControlPlaneBadRequestError,
@@ -130,8 +130,8 @@ const toExecutorSourceInput = ( · 105 unmodified lines
130 version: payload.version,130 version: payload.version,
131 discoveryUrl: payload.discoveryUrl,131 discoveryUrl: payload.discoveryUrl,
132 scopes: payload.scopes,132 scopes: payload.scopes,
133 workspaceOauthClientId: toWorkspaceOauthClientId(133 scopeOauthClientId: toWorkspaceOauthClientId(
134 payload.workspaceOauthClientId,134 payload.scopeOauthClientId,
135 ),135 ),
136 oauthClient: payload.oauthClient,136 oauthClient: payload.oauthClient,
137 name: payload.name,137 name: payload.name,
@@ -1137,7 +1137,7 @@ export const ExecutorSourcesLive = HttpApiBuilder.group( · 999 unmodified lines
1137 Effect.flatMap((executor) =>1137 Effect.flatMap((executor) =>
1138 Effect.gen(function* () {1138 Effect.gen(function* () {
1139 const completed = yield* executor.effect.oauth.completeProviderCallback({1139 const completed = yield* executor.effect.oauth.completeProviderCallback({
1140 workspaceId: path.workspaceId,1140 scopeId: path.workspaceId,
1141 state: urlParams.state,1141 state: urlParams.state,
1142 code: urlParams.code,1142 code: urlParams.code,
1143 error: urlParams.error,1143 error: urlParams.error,
packages/platform/internal/src/tools/executor-admin.ts+103−103
@@ -1,9 +1,9 @@
1import { toTool, type ToolMap } from "@executor/codemode-core";1import { toTool, type ToolMap } from "@executor/codemode-core";
2import type { Executor } from "@executor/platform-sdk";2import type { Executor } from "@executor/platform-sdk";
3import {3import {
4 type AccountId,4 type ScopeId as AccountId,
5 LocalInstallationSchema,5 LocalInstallationSchema,
6 LocalWorkspacePolicySchema,6 LocalScopePolicySchema,
7 SourceDiscoveryResultSchema,7 SourceDiscoveryResultSchema,
8 SourceIdSchema,8 SourceIdSchema,
9 SourceInspectionDiscoverPayloadSchema,9 SourceInspectionDiscoverPayloadSchema,
@@ -11,8 +11,8 @@ import { · 1 unmodified line
11 SourceInspectionSchema,11 SourceInspectionSchema,
12 SourceInspectionToolDetailSchema,12 SourceInspectionToolDetailSchema,
13 SourceSchema,13 SourceSchema,
14 type WorkspaceId,14 type ScopeId as WorkspaceId,
15 WorkspaceOauthClientSchema,15 ScopeOauthClientSchema,
16} from "@executor/platform-sdk/schema";16} from "@executor/platform-sdk/schema";
17import * as Effect from "effect/Effect";17import * as Effect from "effect/Effect";
18import * as Layer from "effect/Layer";18import * as Layer from "effect/Layer";
@@ -25,25 +25,25 @@ import { · 6 unmodified lines
25 SecretMaterialStorerService,25 SecretMaterialStorerService,
26 SecretMaterialUpdaterService,26 SecretMaterialUpdaterService,
27 type ExecutorStateStoreShape,27 type ExecutorStateStoreShape,
28 type WorkspaceInternalToolContext,28 type ScopeInternalToolContext as WorkspaceInternalToolContext,
29 RuntimeSourceAuthService,29 RuntimeSourceAuthService,
30 RuntimeSourceCatalogSyncService,30 RuntimeSourceCatalogSyncService,
31 RuntimeSourceStore,31 RuntimeSourceStore,
32 RuntimeSourceStoreService,32 RuntimeSourceStoreService,
33} from "@executor/platform-sdk/runtime";33} from "@executor/platform-sdk/runtime";
34import {34import {
35 type RuntimeLocalWorkspaceState,35 type RuntimeLocalScopeState,
36 provideOptionalRuntimeLocalWorkspace,36 provideOptionalRuntimeLocalScope,
37} from "../../../sdk/src/runtime/workspace/runtime-context";37} from "../../../sdk/src/runtime/scope/runtime-context";
38import {38import {
39 SourceArtifactStore,39 SourceArtifactStore,
40 type SourceArtifactStoreShape,40 type SourceArtifactStoreShape,
41 WorkspaceConfigStore,41 ScopeConfigStore,
42 type WorkspaceConfigStoreShape,42 type ScopeConfigStoreShape,
43 WorkspaceStateStore,43 ScopeStateStore,
44 type WorkspaceStateStoreShape,44 type ScopeStateStoreShape,
45 makeWorkspaceStorageLayer,45 makeScopeStorageLayer,
46} from "../../../sdk/src/runtime/workspace/storage";46} from "../../../sdk/src/runtime/scope/storage";
47import {47import {
48 CreateSecretPayloadSchema,48 CreateSecretPayloadSchema,
49 CreateSecretResultSchema,49 CreateSecretResultSchema,
@@ -81,9 +81,9 @@ import { · 31 unmodified lines
81 UpdatePolicyPayloadSchema,81 UpdatePolicyPayloadSchema,
82} from "@executor/platform-sdk/contracts";82} from "@executor/platform-sdk/contracts";
83import {83import {
84 CreateWorkspaceOauthClientPayloadSchema,84 CreateScopeOauthClientPayloadSchema,
85 DiscoverSourcePayloadSchema,85 DiscoverSourcePayloadSchema,
86 type CreateWorkspaceOauthClientPayload,86 type CreateScopeOauthClientPayload as CreateWorkspaceOauthClientPayload,
87 UpdateSourcePayloadSchema,87 UpdateSourcePayloadSchema,
88} from "@executor/platform-sdk/contracts";88} from "@executor/platform-sdk/contracts";
89 89
@@ -145,7 +145,7 @@ const inspectDiscoverInputSchema = Schema.standardSchemaV1( · 55 unmodified lines
145 }),145 }),
146);146);
147const listPoliciesOutputSchema = Schema.standardSchemaV1(147const listPoliciesOutputSchema = Schema.standardSchemaV1(
148 Schema.Array(LocalWorkspacePolicySchema),148 Schema.Array(LocalScopePolicySchema),
149);149);
150const policyIdInputSchema = Schema.standardSchemaV1(150const policyIdInputSchema = Schema.standardSchemaV1(
151 Schema.Struct({151 Schema.Struct({
@@ -167,10 +167,10 @@ const workspaceOauthClientListInputSchema = Schema.standardSchemaV1( · 15 unmodified lines
167 }),167 }),
168);168);
169const workspaceOauthClientListOutputSchema = Schema.standardSchemaV1(169const workspaceOauthClientListOutputSchema = Schema.standardSchemaV1(
170 Schema.Array(WorkspaceOauthClientSchema),170 Schema.Array(ScopeOauthClientSchema),
171);171);
172const createWorkspaceOauthClientInputSchema = Schema.standardSchemaV1(172const createWorkspaceOauthClientInputSchema = Schema.standardSchemaV1(
173 CreateWorkspaceOauthClientPayloadSchema,173 CreateScopeOauthClientPayloadSchema,
174);174);
175const removeWorkspaceOauthClientInputSchema = Schema.standardSchemaV1(175const removeWorkspaceOauthClientInputSchema = Schema.standardSchemaV1(
176 Schema.Struct({176 Schema.Struct({
@@ -195,8 +195,8 @@ const sourceInspectionDiscoverOutputSchema = Schema.standardSchemaV1( · 18 unmodified lines
195const sourceDiscoveryOutputSchema = Schema.standardSchemaV1(195const sourceDiscoveryOutputSchema = Schema.standardSchemaV1(
196 SourceDiscoveryResultSchema,196 SourceDiscoveryResultSchema,
197);197);
198const localWorkspacePolicyOutputSchema = Schema.standardSchemaV1(198const localScopePolicyOutputSchema = Schema.standardSchemaV1(
199 LocalWorkspacePolicySchema,199 LocalScopePolicySchema,
200);200);
201 201
202const makeRuntimeLayer = (input: {202const makeRuntimeLayer = (input: {
@@ -205,8 +205,8 @@ const makeRuntimeLayer = (input: { · 2 unmodified lines
205 sourceCatalogSyncService: Effect.Effect.Success<205 sourceCatalogSyncService: Effect.Effect.Success<
206 typeof RuntimeSourceCatalogSyncService206 typeof RuntimeSourceCatalogSyncService
207 >;207 >;
208 workspaceConfigStore: WorkspaceConfigStoreShape;208 scopeConfigStore: ScopeConfigStoreShape;
209 workspaceStateStore: WorkspaceStateStoreShape;209 scopeStateStore: ScopeStateStoreShape;
210 sourceArtifactStore: SourceArtifactStoreShape;210 sourceArtifactStore: SourceArtifactStoreShape;
211 instanceConfigResolver: WorkspaceInternalToolContext["instanceConfigResolver"];211 instanceConfigResolver: WorkspaceInternalToolContext["instanceConfigResolver"];
212 storeSecretMaterial: WorkspaceInternalToolContext["storeSecretMaterial"];212 storeSecretMaterial: WorkspaceInternalToolContext["storeSecretMaterial"];
@@ -224,9 +224,9 @@ const makeRuntimeLayer = (input: { · 11 unmodified lines
224 Layer.succeed(SecretMaterialStorerService, input.storeSecretMaterial),224 Layer.succeed(SecretMaterialStorerService, input.storeSecretMaterial),
225 Layer.succeed(SecretMaterialDeleterService, input.deleteSecretMaterial),225 Layer.succeed(SecretMaterialDeleterService, input.deleteSecretMaterial),
226 Layer.succeed(SecretMaterialUpdaterService, input.updateSecretMaterial),226 Layer.succeed(SecretMaterialUpdaterService, input.updateSecretMaterial),
227 makeWorkspaceStorageLayer({227 makeScopeStorageLayer({
228 workspaceConfigStore: input.workspaceConfigStore,228 scopeConfigStore: input.scopeConfigStore,
229 workspaceStateStore: input.workspaceStateStore,229 scopeStateStore: input.scopeStateStore,
230 sourceArtifactStore: input.sourceArtifactStore,230 sourceArtifactStore: input.sourceArtifactStore,
231 }),231 }),
232 );232 );
@@ -241,34 +241,34 @@ const runRuntimeEffect = <A, E, R>(input: { · 8 unmodified lines
241 | SecretMaterialStorerService241 | SecretMaterialStorerService
242 | SecretMaterialDeleterService242 | SecretMaterialDeleterService
243 | SecretMaterialUpdaterService243 | SecretMaterialUpdaterService
244 | WorkspaceConfigStore244 | ScopeConfigStore
245 | WorkspaceStateStore245 | ScopeStateStore
246 | SourceArtifactStore,246 | SourceArtifactStore,
247 never,247 never,
248 never248 never
249 >;249 >;
250 runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null;250 runtimeLocalScope: RuntimeLocalScopeState | null;
251}) =>251}) =>
252 Effect.runPromise(252 Effect.runPromise(
253 provideOptionalRuntimeLocalWorkspace(253 provideOptionalRuntimeLocalScope(
254 input.effect.pipe(Effect.provide(input.runtimeLayer)),254 input.effect.pipe(Effect.provide(input.runtimeLayer)),
255 input.runtimeLocalWorkspace,255 input.runtimeLocalScope,
256 ) as Effect.Effect<A, E, never>,256 ) as Effect.Effect<A, E, never>,
257 );257 );
258 258
259const runWorkspaceStorageEffect = <A, E, R>(input: {259const runScopeStorageEffect = <A, E, R>(input: {
260 effect: Effect.Effect<A, E, R>;260 effect: Effect.Effect<A, E, R>;
261 workspaceStorageLayer: Layer.Layer<261 scopeStorageLayer: Layer.Layer<
262 WorkspaceConfigStore | WorkspaceStateStore | SourceArtifactStore,262 ScopeConfigStore | ScopeStateStore | SourceArtifactStore,
263 never,263 never,
264 never264 never
265 >;265 >;
266 runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null;266 runtimeLocalScope: RuntimeLocalScopeState | null;
267}) =>267}) =>
268 Effect.runPromise(268 Effect.runPromise(
269 provideOptionalRuntimeLocalWorkspace(269 provideOptionalRuntimeLocalScope(
270 input.effect.pipe(Effect.provide(input.workspaceStorageLayer)),270 input.effect.pipe(Effect.provide(input.scopeStorageLayer)),
271 input.runtimeLocalWorkspace,271 input.runtimeLocalScope,
272 ) as Effect.Effect<A, E, never>,272 ) as Effect.Effect<A, E, never>,
273 );273 );
274 274
@@ -279,17 +279,17 @@ export const createWorkspaceExecutorAdminToolMap = ( · 4 unmodified lines
279 executorStateStore: input.executorStateStore,279 executorStateStore: input.executorStateStore,
280 sourceStore: input.sourceStore,280 sourceStore: input.sourceStore,
281 sourceCatalogSyncService: input.sourceCatalogSyncService,281 sourceCatalogSyncService: input.sourceCatalogSyncService,
282 workspaceConfigStore: input.workspaceConfigStore,282 scopeConfigStore: input.scopeConfigStore,
283 workspaceStateStore: input.workspaceStateStore,283 scopeStateStore: input.scopeStateStore,
284 sourceArtifactStore: input.sourceArtifactStore,284 sourceArtifactStore: input.sourceArtifactStore,
285 instanceConfigResolver: input.instanceConfigResolver,285 instanceConfigResolver: input.instanceConfigResolver,
286 storeSecretMaterial: input.storeSecretMaterial,286 storeSecretMaterial: input.storeSecretMaterial,
287 deleteSecretMaterial: input.deleteSecretMaterial,287 deleteSecretMaterial: input.deleteSecretMaterial,
288 updateSecretMaterial: input.updateSecretMaterial,288 updateSecretMaterial: input.updateSecretMaterial,
289 });289 });
290 const workspaceStorageLayer = makeWorkspaceStorageLayer({290 const scopeStorageLayer = makeScopeStorageLayer({
291 workspaceConfigStore: input.workspaceConfigStore,291 scopeConfigStore: input.scopeConfigStore,
292 workspaceStateStore: input.workspaceStateStore,292 scopeStateStore: input.scopeStateStore,
293 sourceArtifactStore: input.sourceArtifactStore,293 sourceArtifactStore: input.sourceArtifactStore,
294 });294 });
295 295
@@ -306,8 +306,8 @@ export const createWorkspaceExecutorAdminToolMap = ( · 10 unmodified lines
306 inputSchema: emptyInputSchema,306 inputSchema: emptyInputSchema,
307 outputSchema: localInstallationOutputSchema,307 outputSchema: localInstallationOutputSchema,
308 execute: async () => ({308 execute: async () => ({
309 workspaceId: input.workspaceId,309 scopeId: input.scopeId,
310 accountId: input.accountId,310 actorScopeId: input.actorScopeId,
311 }),311 }),
312 },312 },
313 metadata,313 metadata,
@@ -332,7 +332,7 @@ export const createWorkspaceExecutorAdminToolMap = ( · 18 unmodified lines
332 runRuntimeEffect({332 runRuntimeEffect({
333 effect: listLocalSecrets(),333 effect: listLocalSecrets(),
334 runtimeLayer,334 runtimeLayer,
335 runtimeLocalWorkspace: input.runtimeLocalWorkspace,335 runtimeLocalScope: input.runtimeLocalScope,
336 }),336 }),
337 },337 },
338 metadata,338 metadata,
@@ -347,7 +347,7 @@ export const createWorkspaceExecutorAdminToolMap = ( · 8 unmodified lines
347 runRuntimeEffect({347 runRuntimeEffect({
348 effect: createLocalSecret(payload),348 effect: createLocalSecret(payload),
349 runtimeLayer,349 runtimeLayer,
350 runtimeLocalWorkspace: input.runtimeLocalWorkspace,350 runtimeLocalScope: input.runtimeLocalScope,
351 }),351 }),
352 },352 },
353 metadata,353 metadata,
@@ -365,7 +365,7 @@ export const createWorkspaceExecutorAdminToolMap = ( · 11 unmodified lines
365 runRuntimeEffect({365 runRuntimeEffect({
366 effect: updateLocalSecret(payload),366 effect: updateLocalSecret(payload),
367 runtimeLayer,367 runtimeLayer,
368 runtimeLocalWorkspace: input.runtimeLocalWorkspace,368 runtimeLocalScope: input.runtimeLocalScope,
369 }),369 }),
370 },370 },
371 metadata,371 metadata,
@@ -379,7 +379,7 @@ export const createWorkspaceExecutorAdminToolMap = ( · 7 unmodified lines
379 runRuntimeEffect({379 runRuntimeEffect({
380 effect: deleteLocalSecret(secretId),380 effect: deleteLocalSecret(secretId),
381 runtimeLayer,381 runtimeLayer,
382 runtimeLocalWorkspace: input.runtimeLocalWorkspace,382 runtimeLocalScope: input.runtimeLocalScope,
383 }),383 }),
384 },384 },
385 metadata,385 metadata,
@@ -408,11 +408,11 @@ export const createWorkspaceExecutorAdminToolMap = ( · 22 unmodified lines
408 execute: () =>408 execute: () =>
409 runRuntimeEffect({409 runRuntimeEffect({
410 effect: listSources({410 effect: listSources({
411 workspaceId: input.workspaceId,411 scopeId: input.scopeId,
412 accountId: input.accountId as never,412 actorScopeId: input.actorScopeId as never,
413 }),413 }),
414 runtimeLayer,414 runtimeLayer,
415 runtimeLocalWorkspace: input.runtimeLocalWorkspace,415 runtimeLocalScope: input.runtimeLocalScope,
416 }),416 }),
417 },417 },
418 metadata,418 metadata,
@@ -425,12 +425,12 @@ export const createWorkspaceExecutorAdminToolMap = ( · 6 unmodified lines
425 execute: ({ sourceId }: { sourceId: string }) =>425 execute: ({ sourceId }: { sourceId: string }) =>
426 runRuntimeEffect({426 runRuntimeEffect({
427 effect: getSource({427 effect: getSource({
428 workspaceId: input.workspaceId,428 scopeId: input.scopeId,
429 sourceId: sourceId as never,429 sourceId: sourceId as never,
430 accountId: input.accountId as never,430 actorScopeId: input.actorScopeId as never,
431 }),431 }),
432 runtimeLayer,432 runtimeLayer,
433 runtimeLocalWorkspace: input.runtimeLocalWorkspace,433 runtimeLocalScope: input.runtimeLocalScope,
434 }),434 }),
435 },435 },
436 metadata,436 metadata,
@@ -446,13 +446,13 @@ export const createWorkspaceExecutorAdminToolMap = ( · 9 unmodified lines
446 }) =>446 }) =>
447 runRuntimeEffect({447 runRuntimeEffect({
448 effect: updateSource({448 effect: updateSource({
449 workspaceId: input.workspaceId,449 scopeId: input.scopeId,
450 sourceId: payload.sourceId as never,450 sourceId: payload.sourceId as never,
451 accountId: input.accountId as never,451 actorScopeId: input.actorScopeId as never,
452 payload: payload.payload as never,452 payload: payload.payload as never,
453 }),453 }),
454 runtimeLayer,454 runtimeLayer,
455 runtimeLocalWorkspace: input.runtimeLocalWorkspace,455 runtimeLocalScope: input.runtimeLocalScope,
456 }),456 }),
457 },457 },
458 metadata,458 metadata,
@@ -465,11 +465,11 @@ export const createWorkspaceExecutorAdminToolMap = ( · 6 unmodified lines
465 execute: ({ sourceId }: { sourceId: string }) =>465 execute: ({ sourceId }: { sourceId: string }) =>
466 runRuntimeEffect({466 runRuntimeEffect({
467 effect: removeSource({467 effect: removeSource({
468 workspaceId: input.workspaceId,468 scopeId: input.scopeId,
469 sourceId: sourceId as never,469 sourceId: sourceId as never,
470 }),470 }),
471 runtimeLayer,471 runtimeLayer,
472 runtimeLocalWorkspace: input.runtimeLocalWorkspace,472 runtimeLocalScope: input.runtimeLocalScope,
473 }),473 }),
474 },474 },
475 metadata,475 metadata,
@@ -482,11 +482,11 @@ export const createWorkspaceExecutorAdminToolMap = ( · 6 unmodified lines
482 execute: ({ sourceId }: { sourceId: string }) =>482 execute: ({ sourceId }: { sourceId: string }) =>
483 runRuntimeEffect({483 runRuntimeEffect({
484 effect: getSourceInspection({484 effect: getSourceInspection({
485 workspaceId: input.workspaceId,485 scopeId: input.scopeId,
486 sourceId: sourceId as never,486 sourceId: sourceId as never,
487 }),487 }),
488 runtimeLayer,488 runtimeLayer,
489 runtimeLocalWorkspace: input.runtimeLocalWorkspace,489 runtimeLocalScope: input.runtimeLocalScope,
490 }),490 }),
491 },491 },
492 metadata,492 metadata,
@@ -505,12 +505,12 @@ export const createWorkspaceExecutorAdminToolMap = ( · 12 unmodified lines
505 }) =>505 }) =>
506 runRuntimeEffect({506 runRuntimeEffect({
507 effect: getSourceInspectionToolDetail({507 effect: getSourceInspectionToolDetail({
508 workspaceId: input.workspaceId,508 scopeId: input.scopeId,
509 sourceId: sourceId as never,509 sourceId: sourceId as never,
510 toolPath,510 toolPath,
511 }),511 }),
512 runtimeLayer,512 runtimeLayer,
513 runtimeLocalWorkspace: input.runtimeLocalWorkspace,513 runtimeLocalScope: input.runtimeLocalScope,
514 }),514 }),
515 },515 },
516 metadata,516 metadata,
@@ -529,12 +529,12 @@ export const createWorkspaceExecutorAdminToolMap = ( · 12 unmodified lines
529 }) =>529 }) =>
530 runRuntimeEffect({530 runRuntimeEffect({
531 effect: discoverSourceInspectionTools({531 effect: discoverSourceInspectionTools({
532 workspaceId: input.workspaceId,532 scopeId: input.scopeId,
533 sourceId: sourceId as never,533 sourceId: sourceId as never,
534 payload: payload as never,534 payload: payload as never,
535 }),535 }),
536 runtimeLayer,536 runtimeLayer,
537 runtimeLocalWorkspace: input.runtimeLocalWorkspace,537 runtimeLocalScope: input.runtimeLocalScope,
538 }),538 }),
539 },539 },
540 metadata,540 metadata,
@@ -545,13 +545,13 @@ export const createWorkspaceExecutorAdminToolMap = ( · 4 unmodified lines
545 inputSchema: workspaceOauthClientListInputSchema,545 inputSchema: workspaceOauthClientListInputSchema,
546 outputSchema: workspaceOauthClientListOutputSchema,546 outputSchema: workspaceOauthClientListOutputSchema,
547 execute: ({ providerKey }: { providerKey: string }) =>547 execute: ({ providerKey }: { providerKey: string }) =>
548 runWorkspaceStorageEffect({548 runScopeStorageEffect({
549 effect: input.sourceAuthService.listWorkspaceOauthClients({549 effect: input.sourceAuthService.listScopeOauthClients({
550 workspaceId: input.workspaceId,550 scopeId: input.scopeId,
551 providerKey,551 providerKey,
552 }),552 }),
553 workspaceStorageLayer,553 scopeStorageLayer,
554 runtimeLocalWorkspace: input.runtimeLocalWorkspace,554 runtimeLocalScope: input.runtimeLocalScope,
555 }),555 }),
556 },556 },
557 metadata,557 metadata,
@@ -561,17 +561,17 @@ export const createWorkspaceExecutorAdminToolMap = ( · 3 unmodified lines
561 description:561 description:
562 "Create a workspace OAuth client used for shared provider auth flows.",562 "Create a workspace OAuth client used for shared provider auth flows.",
563 inputSchema: createWorkspaceOauthClientInputSchema,563 inputSchema: createWorkspaceOauthClientInputSchema,
564 outputSchema: Schema.standardSchemaV1(WorkspaceOauthClientSchema),564 outputSchema: Schema.standardSchemaV1(ScopeOauthClientSchema),
565 execute: (payload: CreateWorkspaceOauthClientPayload) =>565 execute: (payload: CreateWorkspaceOauthClientPayload) =>
566 runWorkspaceStorageEffect({566 runScopeStorageEffect({
567 effect: input.sourceAuthService.createWorkspaceOauthClient({567 effect: input.sourceAuthService.createScopeOauthClient({
568 workspaceId: input.workspaceId,568 scopeId: input.scopeId,
569 providerKey: payload.providerKey,569 providerKey: payload.providerKey,
570 label: payload.label,570 label: payload.label,
571 oauthClient: payload.oauthClient,571 oauthClient: payload.oauthClient,
572 }),572 }),
573 workspaceStorageLayer,573 scopeStorageLayer,
574 runtimeLocalWorkspace: input.runtimeLocalWorkspace,574 runtimeLocalScope: input.runtimeLocalScope,
575 }),575 }),
576 },576 },
577 metadata,577 metadata,
@@ -582,15 +582,15 @@ export const createWorkspaceExecutorAdminToolMap = ( · 4 unmodified lines
582 inputSchema: removeWorkspaceOauthClientInputSchema,582 inputSchema: removeWorkspaceOauthClientInputSchema,
583 outputSchema: removeResultSchema,583 outputSchema: removeResultSchema,
584 execute: ({ oauthClientId }: { oauthClientId: string }) =>584 execute: ({ oauthClientId }: { oauthClientId: string }) =>
585 runWorkspaceStorageEffect({585 runScopeStorageEffect({
586 effect: input.sourceAuthService586 effect: input.sourceAuthService
587 .removeWorkspaceOauthClient({587 .removeScopeOauthClient({
588 workspaceId: input.workspaceId,588 scopeId: input.scopeId,
589 oauthClientId: oauthClientId as never,589 oauthClientId: oauthClientId as never,
590 })590 })
591 .pipe(Effect.map((removed) => ({ removed }))),591 .pipe(Effect.map((removed) => ({ removed }))),
592 workspaceStorageLayer,592 scopeStorageLayer,
593 runtimeLocalWorkspace: input.runtimeLocalWorkspace,593 runtimeLocalScope: input.runtimeLocalScope,
594 }),594 }),
595 },595 },
596 metadata,596 metadata,
@@ -602,15 +602,15 @@ export const createWorkspaceExecutorAdminToolMap = ( · 5 unmodified lines
602 inputSchema: removeProviderGrantInputSchema,602 inputSchema: removeProviderGrantInputSchema,
603 outputSchema: removeResultSchema,603 outputSchema: removeResultSchema,
604 execute: ({ grantId }: { grantId: string }) =>604 execute: ({ grantId }: { grantId: string }) =>
605 runWorkspaceStorageEffect({605 runScopeStorageEffect({
606 effect: input.sourceAuthService606 effect: input.sourceAuthService
607 .removeProviderAuthGrant({607 .removeProviderAuthGrant({
608 workspaceId: input.workspaceId,608 scopeId: input.scopeId,
609 grantId: grantId as never,609 grantId: grantId as never,
610 })610 })
611 .pipe(Effect.map((removed) => ({ removed }))),611 .pipe(Effect.map((removed) => ({ removed }))),
612 workspaceStorageLayer,612 scopeStorageLayer,
613 runtimeLocalWorkspace: input.runtimeLocalWorkspace,613 runtimeLocalScope: input.runtimeLocalScope,
614 }),614 }),
615 },615 },
616 metadata,616 metadata,
@@ -622,9 +622,9 @@ export const createWorkspaceExecutorAdminToolMap = ( · 5 unmodified lines
622 outputSchema: listPoliciesOutputSchema,622 outputSchema: listPoliciesOutputSchema,
623 execute: () =>623 execute: () =>
624 runRuntimeEffect({624 runRuntimeEffect({
625 effect: listPolicies(input.workspaceId),625 effect: listPolicies(input.scopeId),
626 runtimeLayer,626 runtimeLayer,
627 runtimeLocalWorkspace: input.runtimeLocalWorkspace,627 runtimeLocalScope: input.runtimeLocalScope,
628 }),628 }),
629 },629 },
630 metadata,630 metadata,
@@ -633,15 +633,15 @@ export const createWorkspaceExecutorAdminToolMap = ( · 2 unmodified lines
633 tool: {633 tool: {
634 description: "Create a local workspace policy.",634 description: "Create a local workspace policy.",
635 inputSchema: createPolicyInputSchema,635 inputSchema: createPolicyInputSchema,
636 outputSchema: localWorkspacePolicyOutputSchema,636 outputSchema: localScopePolicyOutputSchema,
637 execute: (payload: CreatePolicyPayload) =>637 execute: (payload: CreatePolicyPayload) =>
638 runRuntimeEffect({638 runRuntimeEffect({
639 effect: createPolicy({639 effect: createPolicy({
640 workspaceId: input.workspaceId,640 scopeId: input.scopeId,
641 payload,641 payload,
642 }),642 }),
643 runtimeLayer,643 runtimeLayer,
644 runtimeLocalWorkspace: input.runtimeLocalWorkspace,644 runtimeLocalScope: input.runtimeLocalScope,
645 }),645 }),
646 },646 },
647 metadata,647 metadata,
@@ -650,15 +650,15 @@ export const createWorkspaceExecutorAdminToolMap = ( · 2 unmodified lines
650 tool: {650 tool: {
651 description: "Get one local workspace policy by id.",651 description: "Get one local workspace policy by id.",
652 inputSchema: policyIdInputSchema,652 inputSchema: policyIdInputSchema,
653 outputSchema: localWorkspacePolicyOutputSchema,653 outputSchema: localScopePolicyOutputSchema,
654 execute: ({ policyId }: { policyId: string }) =>654 execute: ({ policyId }: { policyId: string }) =>
655 runRuntimeEffect({655 runRuntimeEffect({
656 effect: getPolicy({656 effect: getPolicy({
657 workspaceId: input.workspaceId,657 scopeId: input.scopeId,
658 policyId: policyId as never,658 policyId: policyId as never,
659 }),659 }),
660 runtimeLayer,660 runtimeLayer,
661 runtimeLocalWorkspace: input.runtimeLocalWorkspace,661 runtimeLocalScope: input.runtimeLocalScope,
662 }),662 }),
663 },663 },
664 metadata,664 metadata,
@@ -667,7 +667,7 @@ export const createWorkspaceExecutorAdminToolMap = ( · 2 unmodified lines
667 tool: {667 tool: {
668 description: "Update a local workspace policy.",668 description: "Update a local workspace policy.",
669 inputSchema: updatePolicyInputSchema,669 inputSchema: updatePolicyInputSchema,
670 outputSchema: localWorkspacePolicyOutputSchema,670 outputSchema: localScopePolicyOutputSchema,
671 execute: ({671 execute: ({
672 policyId,672 policyId,
673 payload,673 payload,
@@ -677,12 +677,12 @@ export const createWorkspaceExecutorAdminToolMap = ( · 3 unmodified lines
677 }) =>677 }) =>
678 runRuntimeEffect({678 runRuntimeEffect({
679 effect: updatePolicy({679 effect: updatePolicy({
680 workspaceId: input.workspaceId,680 scopeId: input.scopeId,
681 policyId: policyId as never,681 policyId: policyId as never,
682 payload,682 payload,
683 }),683 }),
684 runtimeLayer,684 runtimeLayer,
685 runtimeLocalWorkspace: input.runtimeLocalWorkspace,685 runtimeLocalScope: input.runtimeLocalScope,
686 }),686 }),
687 },687 },
688 metadata,688 metadata,
@@ -695,11 +695,11 @@ export const createWorkspaceExecutorAdminToolMap = ( · 6 unmodified lines
695 execute: ({ policyId }: { policyId: string }) =>695 execute: ({ policyId }: { policyId: string }) =>
696 runRuntimeEffect({696 runRuntimeEffect({
697 effect: removePolicy({697 effect: removePolicy({
698 workspaceId: input.workspaceId,698 scopeId: input.scopeId,
699 policyId: policyId as never,699 policyId: policyId as never,
700 }),700 }),
701 runtimeLayer,701 runtimeLayer,
702 runtimeLocalWorkspace: input.runtimeLocalWorkspace,702 runtimeLocalScope: input.runtimeLocalScope,
703 }),703 }),
704 },704 },
705 metadata,705 metadata,
@@ -903,7 +903,7 @@ export const createExecutorAdminToolMap = (input: { · 197 unmodified lines
903 description:903 description:
904 "Create a workspace OAuth client for a provider-backed source flow.",904 "Create a workspace OAuth client for a provider-backed source flow.",
905 inputSchema: createWorkspaceOauthClientInputSchema,905 inputSchema: createWorkspaceOauthClientInputSchema,
906 outputSchema: Schema.standardSchemaV1(WorkspaceOauthClientSchema),906 outputSchema: Schema.standardSchemaV1(ScopeOauthClientSchema),
907 execute: (payload: CreateWorkspaceOauthClientPayload) =>907 execute: (payload: CreateWorkspaceOauthClientPayload) =>
908 input.executor.sources.oauthClients.create(payload),908 input.executor.sources.oauthClients.create(payload),
909 },909 },
@@ -952,7 +952,7 @@ export const createExecutorAdminToolMap = (input: { · 42 unmodified lines
952 tool: {952 tool: {
953 description: "Create a local workspace policy.",953 description: "Create a local workspace policy.",
954 inputSchema: createPolicyInputSchema,954 inputSchema: createPolicyInputSchema,
955 outputSchema: localWorkspacePolicyOutputSchema,955 outputSchema: localScopePolicyOutputSchema,
956 execute: (payload: CreatePolicyPayload) =>956 execute: (payload: CreatePolicyPayload) =>
957 input.executor.policies.create(payload),957 input.executor.policies.create(payload),
958 },958 },
@@ -962,7 +962,7 @@ export const createExecutorAdminToolMap = (input: { · 3 unmodified lines
962 tool: {962 tool: {
963 description: "Get one local workspace policy by id.",963 description: "Get one local workspace policy by id.",
964 inputSchema: policyIdInputSchema,964 inputSchema: policyIdInputSchema,
965 outputSchema: localWorkspacePolicyOutputSchema,965 outputSchema: localScopePolicyOutputSchema,
966 execute: ({ policyId }: { policyId: string }) =>966 execute: ({ policyId }: { policyId: string }) =>
967 input.executor.policies.get(policyId),967 input.executor.policies.get(policyId),
968 },968 },
@@ -972,7 +972,7 @@ export const createExecutorAdminToolMap = (input: { · 3 unmodified lines
972 tool: {972 tool: {
973 description: "Update a local workspace policy.",973 description: "Update a local workspace policy.",
974 inputSchema: updatePolicyInputSchema,974 inputSchema: updatePolicyInputSchema,
975 outputSchema: localWorkspacePolicyOutputSchema,975 outputSchema: localScopePolicyOutputSchema,
976 execute: ({976 execute: ({
977 policyId,977 policyId,
978 payload,978 payload,
packages/platform/sdk-file/src/config.test.ts+1−1
@@ -34,7 +34,7 @@ describe("local-config", () => { · 33 unmodified lines
34 join(configDirectory, "executor.jsonc"),34 join(configDirectory, "executor.jsonc"),
35 `{35 `{
36 "runtime": "ses",36 "runtime": "ses",
37 // local workspace config37 // local scope config
38 "sources": {38 "sources": {
39 "github": {39 "github": {
40 "kind": "openapi",40 "kind": "openapi",
packages/platform/sdk-file/src/config.ts+5−5
@@ -16,8 +16,8 @@ import { · 15 unmodified lines
16} from "@executor/platform-sdk/schema";16} from "@executor/platform-sdk/schema";
17import * as Effect from "effect/Effect";17import * as Effect from "effect/Effect";
18import * as Schema from "effect/Schema";18import * as Schema from "effect/Schema";
19import type { ExecutorWorkspaceDescriptor } from "@executor/platform-sdk";19import type { ExecutorScopeDescriptor } from "@executor/platform-sdk";
20import type { LoadedLocalExecutorConfig } from "../../sdk/src/runtime/workspace-config";20import type { LoadedLocalExecutorConfig } from "../../sdk/src/runtime/scope-config";
21import {21import {
22 LocalExecutorConfigDecodeError,22 LocalExecutorConfigDecodeError,
23 LocalFileSystemError,23 LocalFileSystemError,
@@ -36,7 +36,7 @@ export const encodeLocalExecutorConfig = (config: LocalExecutorConfig): string = · 12 unmodified lines
36 36
37export const resolveConfigRelativePath = (input: {37export const resolveConfigRelativePath = (input: {
38 path: string;38 path: string;
39 workspaceRoot: string;39 scopeRoot: string;
40}): string => {40}): string => {
41 const trimmed = input.path.trim();41 const trimmed = input.path.trim();
42 if (trimmed.startsWith("~/")) {42 if (trimmed.startsWith("~/")) {
@@ -48,10 +48,10 @@ export const resolveConfigRelativePath = (input: { · 5 unmodified lines
48 if (isAbsolute(trimmed)) {48 if (isAbsolute(trimmed)) {
49 return trimmed;49 return trimmed;
50 }50 }
51 return resolve(input.workspaceRoot, trimmed);51 return resolve(input.scopeRoot, trimmed);
52};52};
53 53
54export type { LoadedLocalExecutorConfig } from "../../sdk/src/runtime/workspace-config";54export type { LoadedLocalExecutorConfig } from "../../sdk/src/runtime/scope-config";
55 55
56const PROJECT_CONFIG_BASENAME = "executor.jsonc";56const PROJECT_CONFIG_BASENAME = "executor.jsonc";
57const PROJECT_CONFIG_DIRECTORY = ".executor";57const PROJECT_CONFIG_DIRECTORY = ".executor";
packages/platform/sdk-file/src/executor-state-store.test.ts+1−1
@@ -52,7 +52,7 @@ describe("local-executor-state-store", () => { · 51 unmodified lines
52 authArtifacts: [],52 authArtifacts: [],
53 authLeases: [],53 authLeases: [],
54 sourceOauthClients: [],54 sourceOauthClients: [],
55 workspaceOauthClients: [],55 scopeOauthClients: [],
56 providerAuthGrants: [],56 providerAuthGrants: [],
57 sourceAuthSessions: [],57 sourceAuthSessions: [],
58 secretMaterials: [],58 secretMaterials: [],
packages/platform/sdk-file/src/executor-state-store.ts+95−95
@@ -20,10 +20,10 @@ import { · 19 unmodified lines
20 type SecretMaterial,20 type SecretMaterial,
21 type SourceAuthSession,21 type SourceAuthSession,
22 SourceAuthSessionSchema,22 SourceAuthSessionSchema,
23 type WorkspaceOauthClient,23 type ScopeOauthClient,
24 WorkspaceOauthClientSchema,24 ScopeOauthClientSchema,
25 type WorkspaceSourceOauthClient,25 type ScopedSourceOauthClient,
26 WorkspaceSourceOauthClientSchema,26 ScopedSourceOauthClientSchema,
27} from "@executor/platform-sdk/schema";27} from "@executor/platform-sdk/schema";
28import * as Effect from "effect/Effect";28import * as Effect from "effect/Effect";
29import * as Option from "effect/Option";29import * as Option from "effect/Option";
@@ -43,8 +43,8 @@ const LocalExecutorStateSnapshotSchema = Schema.Struct({ · 13 unmodified lines
43 version: Schema.Literal(LOCAL_EXECUTOR_STATE_VERSION),43 version: Schema.Literal(LOCAL_EXECUTOR_STATE_VERSION),
44 authArtifacts: Schema.Array(AuthArtifactSchema),44 authArtifacts: Schema.Array(AuthArtifactSchema),
45 authLeases: Schema.Array(AuthLeaseSchema),45 authLeases: Schema.Array(AuthLeaseSchema),
46 sourceOauthClients: Schema.Array(WorkspaceSourceOauthClientSchema),46 sourceOauthClients: Schema.Array(ScopedSourceOauthClientSchema),
47 workspaceOauthClients: Schema.Array(WorkspaceOauthClientSchema),47 scopeOauthClients: Schema.Array(ScopeOauthClientSchema),
48 providerAuthGrants: Schema.Array(ProviderAuthGrantSchema),48 providerAuthGrants: Schema.Array(ProviderAuthGrantSchema),
49 sourceAuthSessions: Schema.Array(SourceAuthSessionSchema),49 sourceAuthSessions: Schema.Array(SourceAuthSessionSchema),
50 secretMaterials: Schema.Array(SecretMaterialSchema),50 secretMaterials: Schema.Array(SecretMaterialSchema),
@@ -69,7 +69,7 @@ const defaultLocalExecutorStateSnapshot = (): LocalExecutorStateSnapshot => ({ · 18 unmodified lines
69 authArtifacts: [],69 authArtifacts: [],
70 authLeases: [],70 authLeases: [],
71 sourceOauthClients: [],71 sourceOauthClients: [],
72 workspaceOauthClients: [],72 scopeOauthClients: [],
73 providerAuthGrants: [],73 providerAuthGrants: [],
74 sourceAuthSessions: [],74 sourceAuthSessions: [],
75 secretMaterials: [],75 secretMaterials: [],
@@ -114,7 +114,7 @@ const localExecutorStatePath = ( · 38 unmodified lines
114 join(114 join(
115 context.homeStateDirectory,115 context.homeStateDirectory,
116 "workspaces",116 "workspaces",
117 deriveLocalInstallation(context).workspaceId,117 deriveLocalInstallation(context).scopeId,
118 LOCAL_EXECUTOR_STATE_BASENAME,118 LOCAL_EXECUTOR_STATE_BASENAME,
119 );119 );
120 120
@@ -230,9 +230,9 @@ const mergeAuthArtifacts = ( · 109 unmodified lines
230 for (const artifact of imported) {230 for (const artifact of imported) {
231 merged.set(231 merged.set(
232 [232 [
233 artifact.workspaceId,233 artifact.scopeId,
234 artifact.sourceId,234 artifact.sourceId,
235 artifact.actorAccountId ?? "",235 artifact.actorScopeId ?? "",
236 artifact.slot,236 artifact.slot,
237 ].join("::"),237 ].join("::"),
238 cloneValue(artifact),238 cloneValue(artifact),
@@ -242,9 +242,9 @@ const mergeAuthArtifacts = ( · 3 unmodified lines
242 for (const artifact of current) {242 for (const artifact of current) {
243 merged.set(243 merged.set(
244 [244 [
245 artifact.workspaceId,245 artifact.scopeId,
246 artifact.sourceId,246 artifact.sourceId,
247 artifact.actorAccountId ?? "",247 artifact.actorScopeId ?? "",
248 artifact.slot,248 artifact.slot,
249 ].join("::"),249 ].join("::"),
250 cloneValue(artifact),250 cloneValue(artifact),
@@ -255,14 +255,14 @@ const mergeAuthArtifacts = ( · 4 unmodified lines
255};255};
256 256
257const mergeSourceOauthClients = (257const mergeSourceOauthClients = (
258 current: readonly WorkspaceSourceOauthClient[],258 current: readonly ScopedSourceOauthClient[],
259 imported: readonly WorkspaceSourceOauthClient[],259 imported: readonly ScopedSourceOauthClient[],
260): WorkspaceSourceOauthClient[] => {260): ScopedSourceOauthClient[] => {
261 const merged = new Map<string, WorkspaceSourceOauthClient>();261 const merged = new Map<string, ScopedSourceOauthClient>();
262 262
263 for (const oauthClient of imported) {263 for (const oauthClient of imported) {
264 merged.set(264 merged.set(
265 [oauthClient.workspaceId, oauthClient.sourceId, oauthClient.providerKey].join(265 [oauthClient.scopeId, oauthClient.sourceId, oauthClient.providerKey].join(
266 "::",266 "::",
267 ),267 ),
268 cloneValue(oauthClient),268 cloneValue(oauthClient),
@@ -271,7 +271,7 @@ const mergeSourceOauthClients = ( · 2 unmodified lines
271 271
272 for (const oauthClient of current) {272 for (const oauthClient of current) {
273 merged.set(273 merged.set(
274 [oauthClient.workspaceId, oauthClient.sourceId, oauthClient.providerKey].join(274 [oauthClient.scopeId, oauthClient.sourceId, oauthClient.providerKey].join(
275 "::",275 "::",
276 ),276 ),
277 cloneValue(oauthClient),277 cloneValue(oauthClient),
@@ -282,21 +282,21 @@ const mergeSourceOauthClients = ( · 4 unmodified lines
282};282};
283 283
284const mergeWorkspaceOauthClients = (284const mergeWorkspaceOauthClients = (
285 current: readonly WorkspaceOauthClient[],285 current: readonly ScopeOauthClient[],
286 imported: readonly WorkspaceOauthClient[],286 imported: readonly ScopeOauthClient[],
287): WorkspaceOauthClient[] => {287): ScopeOauthClient[] => {
288 const merged = new Map<string, WorkspaceOauthClient>();288 const merged = new Map<string, ScopeOauthClient>();
289 289
290 for (const oauthClient of imported) {290 for (const oauthClient of imported) {
291 merged.set(291 merged.set(
292 [oauthClient.workspaceId, oauthClient.providerKey, oauthClient.id].join("::"),292 [oauthClient.scopeId, oauthClient.providerKey, oauthClient.id].join("::"),
293 cloneValue(oauthClient),293 cloneValue(oauthClient),
294 );294 );
295 }295 }
296 296
297 for (const oauthClient of current) {297 for (const oauthClient of current) {
298 merged.set(298 merged.set(
299 [oauthClient.workspaceId, oauthClient.providerKey, oauthClient.id].join("::"),299 [oauthClient.scopeId, oauthClient.providerKey, oauthClient.id].join("::"),
300 cloneValue(oauthClient),300 cloneValue(oauthClient),
301 );301 );
302 }302 }
@@ -326,9 +326,9 @@ export const mergeImportedLocalExecutorStateSnapshot = (input: { · 23 unmodified lines
326 input.current.sourceOauthClients,326 input.current.sourceOauthClients,
327 input.imported.sourceOauthClients ?? [],327 input.imported.sourceOauthClients ?? [],
328 ),328 ),
329 workspaceOauthClients: mergeWorkspaceOauthClients(329 scopeOauthClients: mergeWorkspaceOauthClients(
330 input.current.workspaceOauthClients,330 input.current.scopeOauthClients,
331 input.imported.workspaceOauthClients ?? [],331 input.imported.scopeOauthClients ?? [],
332 ),332 ),
333 providerAuthGrants: mergeProviderAuthGrants(333 providerAuthGrants: mergeProviderAuthGrants(
334 input.current.providerAuthGrants,334 input.current.providerAuthGrants,
@@ -440,40 +440,40 @@ export const createLocalExecutorStateStore = ( · 105 unmodified lines
440 440
441 return {441 return {
442 authArtifacts: {442 authArtifacts: {
443 listByWorkspaceId: (workspaceId: AuthArtifact["workspaceId"]) =>443 listByScopeId: (scopeId: AuthArtifact["scopeId"]) =>
444 stateManager.read((state) =>444 stateManager.read((state) =>
445 sortByUpdatedAtAndIdAsc(445 sortByUpdatedAtAndIdAsc(
446 state.authArtifacts.filter((artifact) => artifact.workspaceId === workspaceId),446 state.authArtifacts.filter((artifact) => artifact.scopeId === scopeId),
447 ),447 ),
448 ),448 ),
449 449
450 listByWorkspaceAndSourceId: (input: {450 listByScopeAndSourceId: (input: {
451 workspaceId: AuthArtifact["workspaceId"];451 scopeId: AuthArtifact["scopeId"];
452 sourceId: AuthArtifact["sourceId"];452 sourceId: AuthArtifact["sourceId"];
453 }) =>453 }) =>
454 stateManager.read((state) =>454 stateManager.read((state) =>
455 sortByUpdatedAtAndIdAsc(455 sortByUpdatedAtAndIdAsc(
456 state.authArtifacts.filter(456 state.authArtifacts.filter(
457 (artifact) =>457 (artifact) =>
458 artifact.workspaceId === input.workspaceId458 artifact.scopeId === input.scopeId
459 && artifact.sourceId === input.sourceId,459 && artifact.sourceId === input.sourceId,
460 ),460 ),
461 ),461 ),
462 ),462 ),
463 463
464 getByWorkspaceSourceAndActor: (input: {464 getByScopeSourceAndActor: (input: {
465 workspaceId: AuthArtifact["workspaceId"];465 scopeId: AuthArtifact["scopeId"];
466 sourceId: AuthArtifact["sourceId"];466 sourceId: AuthArtifact["sourceId"];
467 actorAccountId: AuthArtifact["actorAccountId"];467 actorScopeId: AuthArtifact["actorScopeId"];
468 slot: AuthArtifact["slot"];468 slot: AuthArtifact["slot"];
469 }) =>469 }) =>
470 stateManager.read((state) => {470 stateManager.read((state) => {
471 const artifact = state.authArtifacts.find(471 const artifact = state.authArtifacts.find(
472 (candidate) =>472 (candidate) =>
473 candidate.workspaceId === input.workspaceId473 candidate.scopeId === input.scopeId
474 && candidate.sourceId === input.sourceId474 && candidate.sourceId === input.sourceId
475 && candidate.slot === input.slot475 && candidate.slot === input.slot
476 && actorEquals(candidate.actorAccountId, input.actorAccountId),476 && actorEquals(candidate.actorScopeId, input.actorScopeId),
477 );477 );
478 478
479 return artifact ? Option.some(cloneValue(artifact)) : Option.none<AuthArtifact>();479 return artifact ? Option.some(cloneValue(artifact)) : Option.none<AuthArtifact>();
@@ -484,10 +484,10 @@ export const createLocalExecutorStateStore = ( · 4 unmodified lines
484 const nextArtifacts = state.authArtifacts.filter(484 const nextArtifacts = state.authArtifacts.filter(
485 (candidate) =>485 (candidate) =>
486 !(486 !(
487 candidate.workspaceId === artifact.workspaceId487 candidate.scopeId === artifact.scopeId
488 && candidate.sourceId === artifact.sourceId488 && candidate.sourceId === artifact.sourceId
489 && candidate.slot === artifact.slot489 && candidate.slot === artifact.slot
490 && actorEquals(candidate.actorAccountId, artifact.actorAccountId)490 && actorEquals(candidate.actorScopeId, artifact.actorScopeId)
491 ),491 ),
492 );492 );
493 nextArtifacts.push(cloneValue(artifact));493 nextArtifacts.push(cloneValue(artifact));
@@ -501,18 +501,18 @@ export const createLocalExecutorStateStore = ( · 7 unmodified lines
501 } satisfies StateMutationResult<void>;501 } satisfies StateMutationResult<void>;
502 }),502 }),
503 503
504 removeByWorkspaceSourceAndActor: (input: {504 removeByScopeSourceAndActor: (input: {
505 workspaceId: AuthArtifact["workspaceId"];505 scopeId: AuthArtifact["scopeId"];
506 sourceId: AuthArtifact["sourceId"];506 sourceId: AuthArtifact["sourceId"];
507 actorAccountId: AuthArtifact["actorAccountId"];507 actorScopeId: AuthArtifact["actorScopeId"];
508 slot?: AuthArtifact["slot"];508 slot?: AuthArtifact["slot"];
509 }) =>509 }) =>
510 stateManager.mutate((state) => {510 stateManager.mutate((state) => {
511 const nextArtifacts = state.authArtifacts.filter(511 const nextArtifacts = state.authArtifacts.filter(
512 (candidate) =>512 (candidate) =>
513 candidate.workspaceId !== input.workspaceId513 candidate.scopeId !== input.scopeId
514 || candidate.sourceId !== input.sourceId514 || candidate.sourceId !== input.sourceId
515 || !actorEquals(candidate.actorAccountId, input.actorAccountId)515 || !actorEquals(candidate.actorScopeId, input.actorScopeId)
516 || (input.slot !== undefined && candidate.slot !== input.slot),516 || (input.slot !== undefined && candidate.slot !== input.slot),
517 );517 );
518 518
@@ -525,14 +525,14 @@ export const createLocalExecutorStateStore = ( · 6 unmodified lines
525 } satisfies StateMutationResult<boolean>;525 } satisfies StateMutationResult<boolean>;
526 }),526 }),
527 527
528 removeByWorkspaceAndSourceId: (input: {528 removeByScopeAndSourceId: (input: {
529 workspaceId: AuthArtifact["workspaceId"];529 scopeId: AuthArtifact["scopeId"];
530 sourceId: AuthArtifact["sourceId"];530 sourceId: AuthArtifact["sourceId"];
531 }) =>531 }) =>
532 stateManager.mutate((state) => {532 stateManager.mutate((state) => {
533 const nextArtifacts = state.authArtifacts.filter(533 const nextArtifacts = state.authArtifacts.filter(
534 (candidate) =>534 (candidate) =>
535 candidate.workspaceId !== input.workspaceId535 candidate.scopeId !== input.scopeId
536 || candidate.sourceId !== input.sourceId,536 || candidate.sourceId !== input.sourceId,
537 );537 );
538 538
@@ -591,30 +591,30 @@ export const createLocalExecutorStateStore = ( · 52 unmodified lines
591 },591 },
592 592
593 sourceOauthClients: {593 sourceOauthClients: {
594 getByWorkspaceSourceAndProvider: (input: {594 getByScopeSourceAndProvider: (input: {
595 workspaceId: WorkspaceSourceOauthClient["workspaceId"];595 scopeId: ScopedSourceOauthClient["scopeId"];
596 sourceId: WorkspaceSourceOauthClient["sourceId"];596 sourceId: ScopedSourceOauthClient["sourceId"];
597 providerKey: string;597 providerKey: string;
598 }) =>598 }) =>
599 stateManager.read((state) => {599 stateManager.read((state) => {
600 const oauthClient = state.sourceOauthClients.find(600 const oauthClient = state.sourceOauthClients.find(
601 (candidate) =>601 (candidate) =>
602 candidate.workspaceId === input.workspaceId602 candidate.scopeId === input.scopeId
603 && candidate.sourceId === input.sourceId603 && candidate.sourceId === input.sourceId
604 && candidate.providerKey === input.providerKey,604 && candidate.providerKey === input.providerKey,
605 );605 );
606 606
607 return oauthClient607 return oauthClient
608 ? Option.some(cloneValue(oauthClient))608 ? Option.some(cloneValue(oauthClient))
609 : Option.none<WorkspaceSourceOauthClient>();609 : Option.none<ScopedSourceOauthClient>();
610 }),610 }),
611 611
612 upsert: (oauthClient: WorkspaceSourceOauthClient) =>612 upsert: (oauthClient: ScopedSourceOauthClient) =>
613 stateManager.mutate((state) => {613 stateManager.mutate((state) => {
614 const nextOauthClients = state.sourceOauthClients.filter(614 const nextOauthClients = state.sourceOauthClients.filter(
615 (candidate) =>615 (candidate) =>
616 !(616 !(
617 candidate.workspaceId === oauthClient.workspaceId617 candidate.scopeId === oauthClient.scopeId
618 && candidate.sourceId === oauthClient.sourceId618 && candidate.sourceId === oauthClient.sourceId
619 && candidate.providerKey === oauthClient.providerKey619 && candidate.providerKey === oauthClient.providerKey
620 ),620 ),
@@ -630,14 +630,14 @@ export const createLocalExecutorStateStore = ( · 9 unmodified lines
630 } satisfies StateMutationResult<void>;630 } satisfies StateMutationResult<void>;
631 }),631 }),
632 632
633 removeByWorkspaceAndSourceId: (input: {633 removeByScopeAndSourceId: (input: {
634 workspaceId: WorkspaceSourceOauthClient["workspaceId"];634 scopeId: ScopedSourceOauthClient["scopeId"];
635 sourceId: WorkspaceSourceOauthClient["sourceId"];635 sourceId: ScopedSourceOauthClient["sourceId"];
636 }) =>636 }) =>
637 stateManager.mutate((state) => {637 stateManager.mutate((state) => {
638 const nextOauthClients = state.sourceOauthClients.filter(638 const nextOauthClients = state.sourceOauthClients.filter(
639 (candidate) =>639 (candidate) =>
640 candidate.workspaceId !== input.workspaceId640 candidate.scopeId !== input.scopeId
641 || candidate.sourceId !== input.sourceId,641 || candidate.sourceId !== input.sourceId,
642 );642 );
643 643
@@ -651,35 +651,35 @@ export const createLocalExecutorStateStore = ( · 7 unmodified lines
651 }),651 }),
652 },652 },
653 653
654 workspaceOauthClients: {654 scopeOauthClients: {
655 listByWorkspaceAndProvider: (input: {655 listByScopeAndProvider: (input: {
656 workspaceId: WorkspaceOauthClient["workspaceId"];656 scopeId: ScopeOauthClient["scopeId"];
657 providerKey: string;657 providerKey: string;
658 }) =>658 }) =>
659 stateManager.read((state) =>659 stateManager.read((state) =>
660 sortByUpdatedAtAndIdAsc(660 sortByUpdatedAtAndIdAsc(
661 state.workspaceOauthClients.filter(661 state.scopeOauthClients.filter(
662 (candidate) =>662 (candidate) =>
663 candidate.workspaceId === input.workspaceId663 candidate.scopeId === input.scopeId
664 && candidate.providerKey === input.providerKey,664 && candidate.providerKey === input.providerKey,
665 ),665 ),
666 ),666 ),
667 ),667 ),
668 668
669 getById: (id: WorkspaceOauthClient["id"]) =>669 getById: (id: ScopeOauthClient["id"]) =>
670 stateManager.read((state) => {670 stateManager.read((state) => {
671 const oauthClient = state.workspaceOauthClients.find(671 const oauthClient = state.scopeOauthClients.find(
672 (candidate) => candidate.id === id,672 (candidate) => candidate.id === id,
673 );673 );
674 674
675 return oauthClient675 return oauthClient
676 ? Option.some(cloneValue(oauthClient))676 ? Option.some(cloneValue(oauthClient))
677 : Option.none<WorkspaceOauthClient>();677 : Option.none<ScopeOauthClient>();
678 }),678 }),
679 679
680 upsert: (oauthClient: WorkspaceOauthClient) =>680 upsert: (oauthClient: ScopeOauthClient) =>
681 stateManager.mutate((state) => {681 stateManager.mutate((state) => {
682 const nextOauthClients = state.workspaceOauthClients.filter(682 const nextOauthClients = state.scopeOauthClients.filter(
683 (candidate) => candidate.id !== oauthClient.id,683 (candidate) => candidate.id !== oauthClient.id,
684 );684 );
685 nextOauthClients.push(cloneValue(oauthClient));685 nextOauthClients.push(cloneValue(oauthClient));
@@ -687,50 +687,50 @@ export const createLocalExecutorStateStore = ( · 1 unmodified line
687 return {687 return {
688 state: {688 state: {
689 ...state,689 ...state,
690 workspaceOauthClients: nextOauthClients,690 scopeOauthClients: nextOauthClients,
691 },691 },
692 value: undefined,692 value: undefined,
693 } satisfies StateMutationResult<void>;693 } satisfies StateMutationResult<void>;
694 }),694 }),
695 695
696 removeById: (id: WorkspaceOauthClient["id"]) =>696 removeById: (id: ScopeOauthClient["id"]) =>
697 stateManager.mutate((state) => {697 stateManager.mutate((state) => {
698 const nextOauthClients = state.workspaceOauthClients.filter(698 const nextOauthClients = state.scopeOauthClients.filter(
699 (candidate) => candidate.id !== id,699 (candidate) => candidate.id !== id,
700 );700 );
701 701
702 return {702 return {
703 state: {703 state: {
704 ...state,704 ...state,
705 workspaceOauthClients: nextOauthClients,705 scopeOauthClients: nextOauthClients,
706 },706 },
707 value: nextOauthClients.length !== state.workspaceOauthClients.length,707 value: nextOauthClients.length !== state.scopeOauthClients.length,
708 } satisfies StateMutationResult<boolean>;708 } satisfies StateMutationResult<boolean>;
709 }),709 }),
710 },710 },
711 711
712 providerAuthGrants: {712 providerAuthGrants: {
713 listByWorkspaceId: (workspaceId: ProviderAuthGrant["workspaceId"]) =>713 listByScopeId: (scopeId: ProviderAuthGrant["scopeId"]) =>
714 stateManager.read((state) =>714 stateManager.read((state) =>
715 sortByUpdatedAtAndIdAsc(715 sortByUpdatedAtAndIdAsc(
716 state.providerAuthGrants.filter(716 state.providerAuthGrants.filter(
717 (grant) => grant.workspaceId === workspaceId,717 (grant) => grant.scopeId === scopeId,
718 ),718 ),
719 ),719 ),
720 ),720 ),
721 721
722 listByWorkspaceActorAndProvider: (input: {722 listByScopeActorAndProvider: (input: {
723 workspaceId: ProviderAuthGrant["workspaceId"];723 scopeId: ProviderAuthGrant["scopeId"];
724 actorAccountId: ProviderAuthGrant["actorAccountId"];724 actorScopeId: ProviderAuthGrant["actorScopeId"];
725 providerKey: string;725 providerKey: string;
726 }) =>726 }) =>
727 stateManager.read((state) =>727 stateManager.read((state) =>
728 sortByUpdatedAtAndIdAsc(728 sortByUpdatedAtAndIdAsc(
729 state.providerAuthGrants.filter(729 state.providerAuthGrants.filter(
730 (grant) =>730 (grant) =>
731 grant.workspaceId === input.workspaceId731 grant.scopeId === input.scopeId
732 && grant.providerKey === input.providerKey732 && grant.providerKey === input.providerKey
733 && actorEquals(grant.actorAccountId, input.actorAccountId),733 && actorEquals(grant.actorScopeId, input.actorScopeId),
734 ),734 ),
735 ),735 ),
736 ),736 ),
@@ -782,11 +782,11 @@ export const createLocalExecutorStateStore = ( · 45 unmodified lines
782 listAll: () =>782 listAll: () =>
783 stateManager.read((state) => sortByUpdatedAtAndIdAsc(state.sourceAuthSessions)),783 stateManager.read((state) => sortByUpdatedAtAndIdAsc(state.sourceAuthSessions)),
784 784
785 listByWorkspaceId: (workspaceId: SourceAuthSession["workspaceId"]) =>785 listByScopeId: (scopeId: SourceAuthSession["scopeId"]) =>
786 stateManager.read((state) =>786 stateManager.read((state) =>
787 sortByUpdatedAtAndIdAsc(787 sortByUpdatedAtAndIdAsc(
788 state.sourceAuthSessions.filter(788 state.sourceAuthSessions.filter(
789 (session) => session.workspaceId === workspaceId,789 (session) => session.scopeId === scopeId,
790 ),790 ),
791 ),791 ),
792 ),792 ),
@@ -811,20 +811,20 @@ export const createLocalExecutorStateStore = ( · 18 unmodified lines
811 : Option.none<SourceAuthSession>();811 : Option.none<SourceAuthSession>();
812 }),812 }),
813 813
814 getPendingByWorkspaceSourceAndActor: (input: {814 getPendingByScopeSourceAndActor: (input: {
815 workspaceId: SourceAuthSession["workspaceId"];815 scopeId: SourceAuthSession["scopeId"];
816 sourceId: SourceAuthSession["sourceId"];816 sourceId: SourceAuthSession["sourceId"];
817 actorAccountId: SourceAuthSession["actorAccountId"];817 actorScopeId: SourceAuthSession["actorScopeId"];
818 credentialSlot?: SourceAuthSession["credentialSlot"];818 credentialSlot?: SourceAuthSession["credentialSlot"];
819 }) =>819 }) =>
820 stateManager.read((state) => {820 stateManager.read((state) => {
821 const session = sortByUpdatedAtAndIdAsc(821 const session = sortByUpdatedAtAndIdAsc(
822 state.sourceAuthSessions.filter(822 state.sourceAuthSessions.filter(
823 (candidate) =>823 (candidate) =>
824 candidate.workspaceId === input.workspaceId824 candidate.scopeId === input.scopeId
825 && candidate.sourceId === input.sourceId825 && candidate.sourceId === input.sourceId
826 && candidate.status === "pending"826 && candidate.status === "pending"
827 && actorEquals(candidate.actorAccountId, input.actorAccountId)827 && actorEquals(candidate.actorScopeId, input.actorScopeId)
828 && (input.credentialSlot === undefined828 && (input.credentialSlot === undefined
829 || candidate.credentialSlot === input.credentialSlot),829 || candidate.credentialSlot === input.credentialSlot),
830 ),830 ),
@@ -846,7 +846,7 @@ export const createLocalExecutorStateStore = ( · 15 unmodified lines
846 846
847 update: (847 update: (
848 id: SourceAuthSession["id"],848 id: SourceAuthSession["id"],
849 patch: Partial<Omit<SourceAuthSession, "id" | "workspaceId" | "sourceId" | "createdAt">>,849 patch: Partial<Omit<SourceAuthSession, "id" | "scopeId" | "sourceId" | "createdAt">>,
850 ) =>850 ) =>
851 stateManager.mutate((state) => {851 stateManager.mutate((state) => {
852 let updated: SourceAuthSession | null = null;852 let updated: SourceAuthSession | null = null;
@@ -887,14 +887,14 @@ export const createLocalExecutorStateStore = ( · 34 unmodified lines
887 } satisfies StateMutationResult<void>;887 } satisfies StateMutationResult<void>;
888 }),888 }),
889 889
890 removeByWorkspaceAndSourceId: (890 removeByScopeAndSourceId: (
891 workspaceId: SourceAuthSession["workspaceId"],891 scopeId: SourceAuthSession["scopeId"],
892 sourceId: SourceAuthSession["sourceId"],892 sourceId: SourceAuthSession["sourceId"],
893 ) =>893 ) =>
894 stateManager.mutate((state) => {894 stateManager.mutate((state) => {
895 const nextSessions = state.sourceAuthSessions.filter(895 const nextSessions = state.sourceAuthSessions.filter(
896 (candidate) =>896 (candidate) =>
897 candidate.workspaceId !== workspaceId || candidate.sourceId !== sourceId,897 candidate.scopeId !== scopeId || candidate.sourceId !== sourceId,
898 );898 );
899 899
900 return {900 return {
@@ -993,14 +993,14 @@ export const createLocalExecutorStateStore = ( · 92 unmodified lines
993 : Option.none<Execution>();993 : Option.none<Execution>();
994 }),994 }),
995 995
996 getByWorkspaceAndId: (996 getByScopeAndId: (
997 workspaceId: Execution["workspaceId"],997 scopeId: Execution["scopeId"],
998 executionId: Execution["id"],998 executionId: Execution["id"],
999 ) =>999 ) =>
1000 stateManager.read((state) => {1000 stateManager.read((state) => {
1001 const execution = state.executions.find(1001 const execution = state.executions.find(
1002 (candidate) =>1002 (candidate) =>
1003 candidate.workspaceId === workspaceId && candidate.id === executionId,1003 candidate.scopeId === scopeId && candidate.id === executionId,
1004 );1004 );
1005 return execution1005 return execution
1006 ? Option.some(cloneValue(execution))1006 ? Option.some(cloneValue(execution))
@@ -1019,7 +1019,7 @@ export const createLocalExecutorStateStore = ( · 12 unmodified lines
1019 update: (1019 update: (
1020 executionId: Execution["id"],1020 executionId: Execution["id"],
1021 patch: Partial<1021 patch: Partial<
1022 Omit<Execution, "id" | "workspaceId" | "createdByAccountId" | "createdAt">1022 Omit<Execution, "id" | "scopeId" | "createdByAccountId" | "createdAt">
1023 >,1023 >,
1024 ) =>1024 ) =>
1025 stateManager.mutate((state) => {1025 stateManager.mutate((state) => {
packages/platform/sdk-file/src/index.ts+8−8
@@ -10,7 +10,7 @@ import { · 9 unmodified lines
10 type ExecutorBackendServices,10 type ExecutorBackendServices,
11 type ExecutorLocalToolBackend,11 type ExecutorLocalToolBackend,
12 type ExecutorSourceTypeDeclarationsBackend,12 type ExecutorSourceTypeDeclarationsBackend,
13 type ExecutorWorkspaceDescriptor,13 type ExecutorScopeDescriptor,
14} from "@executor/platform-sdk";14} from "@executor/platform-sdk";
15import type {15import type {
16 LocalExecutorConfig,16 LocalExecutorConfig,
@@ -198,11 +198,11 @@ const createBoundSourceTypeDeclarationsRefresher = ( · 181 unmodified lines
198 }),198 }),
199});199});
200 200
201const toExecutorWorkspaceContext = (201const toExecutorScopeContext = (
202 context: ResolvedLocalWorkspaceContext,202 context: ResolvedLocalWorkspaceContext,
203): ExecutorWorkspaceDescriptor => ({203): ExecutorScopeDescriptor => ({
204 workspaceName: context.workspaceName,204 scopeName: context.workspaceName,
205 workspaceRoot: context.workspaceRoot,205 scopeRoot: context.workspaceRoot,
206 metadata: {206 metadata: {
207 kind: "file",207 kind: "file",
208 configDirectory: context.configDirectory,208 configDirectory: context.configDirectory,
@@ -248,11 +248,11 @@ export const createLocalExecutorServicesEffect = ( · 39 unmodified lines
248 });248 });
249 249
250 return {250 return {
251 workspace: toExecutorWorkspaceContext(workspaceContext),251 scope: toExecutorScopeContext(workspaceContext),
252 storage: {252 storage: {
253 installation: createBoundInstallationStore(workspaceContext),253 installation: createBoundInstallationStore(workspaceContext),
254 workspaceConfig: workspaceConfigStore,254 scopeConfig: workspaceConfigStore,
255 workspaceState: createBoundWorkspaceStateStore(255 scopeState: createBoundWorkspaceStateStore(
256 workspaceContext,256 workspaceContext,
257 fileSystem,257 fileSystem,
258 ),258 ),
packages/platform/sdk-file/src/installation.test.ts+4−4
@@ -42,8 +42,8 @@ describe("local-installation", () => { · 41 unmodified lines
42 const installation = runtime.localInstallation;42 const installation = runtime.localInstallation;
43 const fs = yield* FileSystem.FileSystem;43 const fs = yield* FileSystem.FileSystem;
44 44
45 expect(installation.accountId).toBe("acc_local_default");45 expect(installation.actorScopeId).toBe("acc_local_default");
46 expect(installation.workspaceId.startsWith("ws_local_")).toBe(true);46 expect(installation.scopeId.startsWith("ws_local_")).toBe(true);
47 expect(yield* fs.exists(join(workspaceRoot, ".executor", "executor.jsonc"))).toBe(false);47 expect(yield* fs.exists(join(workspaceRoot, ".executor", "executor.jsonc"))).toBe(false);
48 }).pipe(Effect.provide(NodeFileSystem.layer)),48 }).pipe(Effect.provide(NodeFileSystem.layer)),
49 60_000,49 60_000,
@@ -63,8 +63,8 @@ describe("local-installation", () => { · 13 unmodified lines
63 context,63 context,
64 });64 });
65 65
66 expect(second.accountId).toBe(first.accountId);66 expect(second.actorScopeId).toBe(first.actorScopeId);
67 expect(second.workspaceId).toBe(first.workspaceId);67 expect(second.scopeId).toBe(first.scopeId);
68 }).pipe(Effect.provide(NodeFileSystem.layer)),68 }).pipe(Effect.provide(NodeFileSystem.layer)),
69 );69 );
70});70});
packages/platform/sdk-file/src/installation.ts+9−6
@@ -1,15 +1,14 @@
1import { createHash } from "node:crypto";1import { createHash } from "node:crypto";
2 2
3import {3import {
4 AccountIdSchema,4 ScopeIdSchema,
5 WorkspaceIdSchema,
6 type LocalInstallation,5 type LocalInstallation,
7} from "@executor/platform-sdk/schema";6} from "@executor/platform-sdk/schema";
8import * as Effect from "effect/Effect";7import * as Effect from "effect/Effect";
9 8
10import type { ResolvedLocalWorkspaceContext } from "./config";9import type { ResolvedLocalWorkspaceContext } from "./config";
11 10
12const LOCAL_ACCOUNT_ID = AccountIdSchema.make("acc_local_default");11const LOCAL_ACCOUNT_ID = ScopeIdSchema.make("acc_local_default");
13 12
14const stableHash = (value: string): string =>13const stableHash = (value: string): string =>
15 createHash("sha256").update(value).digest("hex").slice(0, 16);14 createHash("sha256").update(value).digest("hex").slice(0, 16);
@@ -18,15 +17,19 @@ const normalizeSlashPath = (value: string): string => · 2 unmodified lines
18 value.replaceAll("\\", "/");17 value.replaceAll("\\", "/");
19 18
20const deriveWorkspaceId = (context: ResolvedLocalWorkspaceContext) =>19const deriveWorkspaceId = (context: ResolvedLocalWorkspaceContext) =>
21 WorkspaceIdSchema.make(20 ScopeIdSchema.make(
22 `ws_local_${stableHash(normalizeSlashPath(context.workspaceRoot))}`,21 `ws_local_${stableHash(normalizeSlashPath(context.workspaceRoot))}`,
23 );22 );
24 23
25export const deriveLocalInstallation = (24export const deriveLocalInstallation = (
26 context: ResolvedLocalWorkspaceContext,25 context: ResolvedLocalWorkspaceContext,
27): LocalInstallation => ({26): LocalInstallation => ({
28 accountId: LOCAL_ACCOUNT_ID,27 actorScopeId: LOCAL_ACCOUNT_ID,
29 workspaceId: deriveWorkspaceId(context),28 scopeId: deriveWorkspaceId(context),
29 resolutionScopeIds: [
30 deriveWorkspaceId(context),
31 LOCAL_ACCOUNT_ID,
32 ],
30});33});
31 34
32export const loadLocalInstallation = (35export const loadLocalInstallation = (
packages/platform/sdk-file/src/secret-material-providers.ts+7−7
@@ -28,9 +28,9 @@ import { · 27 unmodified lines
28 type StoreSecretMaterial,28 type StoreSecretMaterial,
29 type DeleteSecretMaterial,29 type DeleteSecretMaterial,
30 type UpdateSecretMaterial,30 type UpdateSecretMaterial,
31} from "../../sdk/src/runtime/workspace/secret-material-providers";31} from "../../sdk/src/runtime/scope/secret-material-providers";
32import { fromConfigSecretProviderId } from "../../sdk/src/runtime/workspace/config-secrets";32import { fromConfigSecretProviderId } from "../../sdk/src/runtime/scope/config-secrets";
33import { getRuntimeLocalWorkspaceOption } from "../../sdk/src/runtime/workspace/runtime-context";33import { getRuntimeLocalScopeOption } from "../../sdk/src/runtime/scope/runtime-context";
34import {34import {
35 ExecutorStateStore,35 ExecutorStateStore,
36 type ExecutorStateStoreShape,36 type ExecutorStateStoreShape,
@@ -876,7 +876,7 @@ const readFileSecretValue = (input: { · 839 unmodified lines
876 const fs = yield* FileSystem.FileSystem;876 const fs = yield* FileSystem.FileSystem;
877 const resolvedPath = resolveConfigRelativePath({877 const resolvedPath = resolveConfigRelativePath({
878 path: input.provider.path,878 path: input.provider.path,
879 workspaceRoot: input.workspaceRoot,879 scopeRoot: input.workspaceRoot,
880 });880 });
881 const raw = yield* fs.readFileString(resolvedPath, "utf8").pipe(881 const raw = yield* fs.readFileString(resolvedPath, "utf8").pipe(
882 Effect.mapError(toError),882 Effect.mapError(toError),
@@ -1140,14 +1140,14 @@ const resolveRuntimeSecretMaterialConfig = (input: { · 257 unmodified lines
1140 workspaceRoot?: string | null;1140 workspaceRoot?: string | null;
1141}) =>1141}) =>
1142 Effect.gen(function* () {1142 Effect.gen(function* () {
1143 const runtimeLocalWorkspace = yield* getRuntimeLocalWorkspaceOption();1143 const runtimeLocalScope = yield* getRuntimeLocalScopeOption();
1144 1144
1145 return {1145 return {
1146 localConfig: input.localConfig1146 localConfig: input.localConfig
1147 ?? runtimeLocalWorkspace?.loadedConfig.config1147 ?? runtimeLocalScope?.loadedConfig.config
1148 ?? null,1148 ?? null,
1149 workspaceRoot: input.workspaceRoot1149 workspaceRoot: input.workspaceRoot
1150 ?? runtimeLocalWorkspace?.workspace.workspaceRoot1150 ?? runtimeLocalScope?.scope.scopeRoot
1151 ?? null,1151 ?? null,
1152 };1152 };
1153 });1153 });
packages/platform/sdk-file/src/source-artifacts.test.ts+2−2
@@ -7,7 +7,7 @@ import * as Effect from "effect/Effect"; · 6 unmodified lines
7 7
8import {8import {
9 SourceIdSchema,9 SourceIdSchema,
10 WorkspaceIdSchema,10 ScopeIdSchema,
11 type Source,11 type Source,
12} from "@executor/platform-sdk/schema";12} from "@executor/platform-sdk/schema";
13 13
@@ -52,7 +52,7 @@ const makeContext = (): Effect.Effect< · 38 unmodified lines
52 52
53const makeSource = (): Source => ({53const makeSource = (): Source => ({
54 id: SourceIdSchema.make("src_test"),54 id: SourceIdSchema.make("src_test"),
55 workspaceId: WorkspaceIdSchema.make("ws_test"),55 scopeId: ScopeIdSchema.make("ws_test"),
56 name: "Test Source",56 name: "Test Source",
57 kind: "openapi",57 kind: "openapi",
58 endpoint: "https://example.com/api",58 endpoint: "https://example.com/api",
packages/platform/sdk-file/src/source-type-declarations.test.ts+2−3
@@ -7,7 +7,7 @@ import * as Effect from "effect/Effect"; · 6 unmodified lines
7 7
8import {8import {
9 SourceIdSchema,9 SourceIdSchema,
10 WorkspaceIdSchema,10 ScopeIdSchema,
11 type Source,11 type Source,
12} from "@executor/platform-sdk/schema";12} from "@executor/platform-sdk/schema";
13 13
@@ -21,7 +21,6 @@ import { · 7 unmodified lines
21 ExecutableIdSchema,21 ExecutableIdSchema,
22 ResourceIdSchema,22 ResourceIdSchema,
23 ResponseSetIdSchema,23 ResponseSetIdSchema,
24 ScopeIdSchema,
25 ShapeSymbolIdSchema,24 ShapeSymbolIdSchema,
26} from "@executor/ir/ids";25} from "@executor/ir/ids";
27import type { CatalogV1, ProvenanceRef } from "@executor/ir/model";26import type { CatalogV1, ProvenanceRef } from "@executor/ir/model";
@@ -397,7 +396,7 @@ const makeSource = (input: { · 369 unmodified lines
397 status?: Source["status"];396 status?: Source["status"];
398}): Source => ({397}): Source => ({
399 id: SourceIdSchema.make(input.id),398 id: SourceIdSchema.make(input.id),
400 workspaceId: WorkspaceIdSchema.make("ws_test"),399 scopeId: ScopeIdSchema.make("ws_test"),
401 name: input.id,400 name: input.id,
402 kind: "graphql",401 kind: "graphql",
403 endpoint: "https://api.linear.app/graphql",402 endpoint: "https://api.linear.app/graphql",
packages/platform/sdk-file/src/workspace-state.ts+9−9
@@ -3,11 +3,11 @@ import { FileSystem } from "@effect/platform"; · 2 unmodified lines
3import * as Effect from "effect/Effect";3import * as Effect from "effect/Effect";
4import * as Schema from "effect/Schema";4import * as Schema from "effect/Schema";
5 5
6import type { LocalWorkspaceState } from "../../sdk/src/runtime/workspace-state";6import type { LocalScopeState } from "../../sdk/src/runtime/scope-state";
7import {7import {
8 defaultLocalWorkspaceState,8 defaultLocalScopeState,
9 LocalWorkspaceStateSchema,9 LocalScopeStateSchema,
10} from "../../sdk/src/runtime/workspace-state";10} from "../../sdk/src/runtime/scope-state";
11import type { ResolvedLocalWorkspaceContext } from "./config";11import type { ResolvedLocalWorkspaceContext } from "./config";
12import {12import {
13 LocalFileSystemError,13 LocalFileSystemError,
@@ -17,7 +17,7 @@ import { · 3 unmodified lines
17 17
18const WORKSPACE_STATE_BASENAME = "workspace-state.json";18const WORKSPACE_STATE_BASENAME = "workspace-state.json";
19 19
20const decodeLocalWorkspaceState = Schema.decodeUnknownSync(LocalWorkspaceStateSchema);20const decodeLocalWorkspaceState = Schema.decodeUnknownSync(LocalScopeStateSchema);
21 21
22const mapFileSystemError = (path: string, action: string) => (cause: unknown) =>22const mapFileSystemError = (path: string, action: string) => (cause: unknown) =>
23 new LocalFileSystemError({23 new LocalFileSystemError({
@@ -34,7 +34,7 @@ export const localWorkspaceStatePath = ( · 10 unmodified lines
34export const loadLocalWorkspaceState = (34export const loadLocalWorkspaceState = (
35 context: ResolvedLocalWorkspaceContext,35 context: ResolvedLocalWorkspaceContext,
36): Effect.Effect<36): Effect.Effect<
37 LocalWorkspaceState,37 LocalScopeState,
38 LocalFileSystemError | LocalWorkspaceStateDecodeError,38 LocalFileSystemError | LocalWorkspaceStateDecodeError,
39 FileSystem.FileSystem39 FileSystem.FileSystem
40> =>40> =>
@@ -45,7 +45,7 @@ export const loadLocalWorkspaceState = ( · 4 unmodified lines
45 Effect.mapError(mapFileSystemError(path, "check workspace state path")),45 Effect.mapError(mapFileSystemError(path, "check workspace state path")),
46 );46 );
47 if (!exists) {47 if (!exists) {
48 return defaultLocalWorkspaceState();48 return defaultLocalScopeState();
49 }49 }
50 50
51 const content = yield* fs.readFileString(path, "utf8").pipe(51 const content = yield* fs.readFileString(path, "utf8").pipe(
@@ -55,7 +55,7 @@ export const loadLocalWorkspaceState = ( · 3 unmodified lines
55 try: () => decodeLocalWorkspaceState(JSON.parse(content) as unknown),55 try: () => decodeLocalWorkspaceState(JSON.parse(content) as unknown),
56 catch: (cause) =>56 catch: (cause) =>
57 new LocalWorkspaceStateDecodeError({57 new LocalWorkspaceStateDecodeError({
58 message: `Invalid local workspace state at ${path}: ${unknownLocalErrorDetails(cause)}`,58 message: `Invalid local scope state at ${path}: ${unknownLocalErrorDetails(cause)}`,
59 path,59 path,
60 details: unknownLocalErrorDetails(cause),60 details: unknownLocalErrorDetails(cause),
61 }),61 }),
@@ -64,7 +64,7 @@ export const loadLocalWorkspaceState = ( · 2 unmodified lines
64 64
65export const writeLocalWorkspaceState = (input: {65export const writeLocalWorkspaceState = (input: {
66 context: ResolvedLocalWorkspaceContext;66 context: ResolvedLocalWorkspaceContext;
67 state: LocalWorkspaceState;67 state: LocalScopeState;
68}): Effect.Effect<void, LocalFileSystemError, FileSystem.FileSystem> =>68}): Effect.Effect<void, LocalFileSystemError, FileSystem.FileSystem> =>
69 Effect.gen(function* () {69 Effect.gen(function* () {
70 const fs = yield* FileSystem.FileSystem;70 const fs = yield* FileSystem.FileSystem;
packages/platform/sdk/package.json+1−0
@@ -42,6 +42,7 @@ · 41 unmodified lines
42 },42 },
43 "devDependencies": {43 "devDependencies": {
44 "@effect/vitest": "catalog:",44 "@effect/vitest": "catalog:",
45 "@executor/effect-test-utils": "workspace:*",
45 "@modelcontextprotocol/sdk": "^1.26.0",46 "@modelcontextprotocol/sdk": "^1.26.0",
46 "@types/node": "catalog:",47 "@types/node": "catalog:",
47 "bun-types": "catalog:",48 "bun-types": "catalog:",
packages/platform/sdk/src/backend.ts+14−14
@@ -6,8 +6,8 @@ import { · 5 unmodified lines
6 type BoundLocalToolRuntimeLoader,6 type BoundLocalToolRuntimeLoader,
7 type BoundSourceArtifactStore,7 type BoundSourceArtifactStore,
8 type BoundSourceTypeDeclarationsRefresher,8 type BoundSourceTypeDeclarationsRefresher,
9 type BoundWorkspaceConfigStore,9 type BoundScopeConfigStore,
10 type BoundWorkspaceStateStore,10 type BoundScopeStateStore,
11 type ExecutorRuntime,11 type ExecutorRuntime,
12 type ExecutorRuntimeOptions,12 type ExecutorRuntimeOptions,
13 type RuntimeInstanceConfigService,13 type RuntimeInstanceConfigService,
@@ -15,13 +15,13 @@ import { · 1 unmodified line
15 type RuntimeSecretMaterialServices,15 type RuntimeSecretMaterialServices,
16} from "./runtime";16} from "./runtime";
17import type {17import type {
18 ExecutorWorkspaceContext,18 ExecutorScopeContext,
19 ExecutorWorkspaceDescriptor,19 ExecutorScopeDescriptor,
20} from "./workspace";20} from "./scope";
21export type {21export type {
22 ExecutorWorkspaceContext,22 ExecutorScopeContext,
23 ExecutorWorkspaceDescriptor,23 ExecutorScopeDescriptor,
24} from "./workspace";24} from "./scope";
25 25
26export type ExecutorBackend = {26export type ExecutorBackend = {
27 createRuntime: (27 createRuntime: (
@@ -30,8 +30,8 @@ export type ExecutorBackend = { · 2 unmodified lines
30};30};
31 31
32export type ExecutorInstallationBackend = BoundInstallationStore;32export type ExecutorInstallationBackend = BoundInstallationStore;
33export type ExecutorWorkspaceConfigBackend = BoundWorkspaceConfigStore;33export type ExecutorScopeConfigBackend = BoundScopeConfigStore;
34export type ExecutorWorkspaceStateBackend = BoundWorkspaceStateStore;34export type ExecutorScopeStateBackend = BoundScopeStateStore;
35export type ExecutorSourceArtifactBackend = BoundSourceArtifactStore;35export type ExecutorSourceArtifactBackend = BoundSourceArtifactStore;
36export type ExecutorStateBackend = import("./runtime").ExecutorStateStoreShape;36export type ExecutorStateBackend = import("./runtime").ExecutorStateStoreShape;
37export type ExecutorLocalToolBackend = BoundLocalToolRuntimeLoader;37export type ExecutorLocalToolBackend = BoundLocalToolRuntimeLoader;
@@ -42,8 +42,8 @@ export type ExecutorInstanceConfigBackend = RuntimeInstanceConfigService; · 4 unmodified lines
42 42
43export type ExecutorStorageBackend = {43export type ExecutorStorageBackend = {
44 installation: ExecutorInstallationBackend;44 installation: ExecutorInstallationBackend;
45 workspaceConfig: ExecutorWorkspaceConfigBackend;45 scopeConfig: ExecutorScopeConfigBackend;
46 workspaceState: ExecutorWorkspaceStateBackend;46 scopeState: ExecutorScopeStateBackend;
47 sourceArtifacts: ExecutorSourceArtifactBackend;47 sourceArtifacts: ExecutorSourceArtifactBackend;
48 executorState: ExecutorStateBackend;48 executorState: ExecutorStateBackend;
49 secretMaterial: ExecutorSecretMaterialBackend;49 secretMaterial: ExecutorSecretMaterialBackend;
@@ -51,7 +51,7 @@ export type ExecutorStorageBackend = { · 1 unmodified line
51};51};
52 52
53export type ExecutorBackendServices = {53export type ExecutorBackendServices = {
54 workspace: ExecutorWorkspaceDescriptor;54 scope: ExecutorScopeDescriptor;
55 storage: ExecutorStorageBackend;55 storage: ExecutorStorageBackend;
56 instanceConfig: ExecutorInstanceConfigBackend;56 instanceConfig: ExecutorInstanceConfigBackend;
57 localTools?: ExecutorLocalToolBackend;57 localTools?: ExecutorLocalToolBackend;
@@ -68,7 +68,7 @@ export const createExecutorBackend = (input: { · 10 unmodified lines
68 createExecutorRuntimeFromServices({68 createExecutorRuntimeFromServices({
69 ...options,69 ...options,
70 services: {70 services: {
71 workspace: services.workspace,71 scope: services.scope,
72 storage: services.storage satisfies RuntimeStorageServices,72 storage: services.storage satisfies RuntimeStorageServices,
73 localToolRuntimeLoader: services.localTools,73 localToolRuntimeLoader: services.localTools,
74 sourceTypeDeclarationsRefresher: services.sourceTypeDeclarations,74 sourceTypeDeclarationsRefresher: services.sourceTypeDeclarations,
packages/platform/sdk/src/errors.ts+3−1
@@ -1,4 +1,6 @@
1import { HttpApiSchema } from "@effect/platform";1import {
2 HttpApiSchema,
3} from "@effect/platform";
2import * as Schema from "effect/Schema";4import * as Schema from "effect/Schema";
3 5
4export class ControlPlaneBadRequestError extends Schema.TaggedError<ControlPlaneBadRequestError>()(6export class ControlPlaneBadRequestError extends Schema.TaggedError<ControlPlaneBadRequestError>()(
packages/platform/sdk/src/executions/contracts.ts+3−1
@@ -1,6 +1,8 @@
1import * as Schema from "effect/Schema";1import * as Schema from "effect/Schema";
2 2
3import { TrimmedNonEmptyStringSchema } from "../string-schemas";3import {
4 TrimmedNonEmptyStringSchema,
5} from "../string-schemas";
4 6
5export const CreateExecutionPayloadSchema = Schema.Struct({7export const CreateExecutionPayloadSchema = Schema.Struct({
6 code: TrimmedNonEmptyStringSchema,8 code: TrimmedNonEmptyStringSchema,
packages/platform/sdk/src/executor.test.ts+453−0
@@ -0,0 +1,453 @@
1import { join } from "node:path";
2import {
3 HttpApi,
4 HttpApiEndpoint,
5 HttpApiGroup,
6 HttpApiSchema,
7 HttpApiBuilder,
8 HttpApiClient,
9 FileSystem,
10} from "@effect/platform";
11import {
12 NodeFileSystem,
13 NodeHttpServer,
14} from "@effect/platform-node";
15import {
16 describe,
17 expect,
18 it,
19} from "@effect/vitest";
20import * as Either from "effect/Either";
21import {
22 assertInclude,
23} from "@effect/vitest/utils";
24import * as Effect from "effect/Effect";
25import * as Layer from "effect/Layer";
26import * as Schema from "effect/Schema";
27import {
28 makeOpenApiTestServer,
29} from "@executor/effect-test-utils";
30
31import {
32 createExecutorBackend,
33 createExecutorEffect,
34 type Executor,
35} from "./index";
36import {
37 type LocalInstallation,
38 ScopeIdSchema,
39} from "./schema";
40import {
41 createLocalExecutorServicesEffect,
42} from "../../sdk-file/src/index";
43import {
44 ExecutorApi,
45 createExecutorApiLayer,
46} from "../../api/src/index";
47
48type LocalExecutorServices = Effect.Effect.Success<
49 ReturnType<typeof createLocalExecutorServicesEffect>
50>;
51
52const createClientLayer = (executor: Executor) =>
53 HttpApiBuilder.serve().pipe(
54 Layer.provide(createExecutorApiLayer(executor)),
55 Layer.provideMerge(NodeHttpServer.layerTest),
56 );
57
58const createExecutorApiClient = () =>
59 HttpApiClient.make(ExecutorApi, {
60 });
61
62type ExecutorApiClient = Effect.Effect.Success<
63 ReturnType<typeof createExecutorApiClient>
64>;
65
66const withExecutorApiClient = <A, E>(
67 executor: Executor,
68 f: (client: ExecutorApiClient) => Effect.Effect<A, E, never>,
69): Effect.Effect<A, E, never> =>
70 Effect.gen(function* () {
71 const client = yield* createExecutorApiClient();
72 return yield* f(client);
73 }).pipe(Effect.provide(createClientLayer(executor).pipe(Layer.orDie)));
74
75const expectLeft = <A, E>(effect: Effect.Effect<A, E, never>) =>
76 Effect.either(effect).pipe(
77 Effect.flatMap((result) =>
78 Either.isLeft(result)
79 ? Effect.succeed(result.left)
80 : Effect.die("Expected effect to fail"),
81 ),
82 );
83
84type OpenApiSpecServer = {
85 baseUrl: string;
86 specUrl: string;
87 close: () => Promise<void>;
88};
89
90const ownerParam = HttpApiSchema.param("owner", Schema.String);
91const repoParam = HttpApiSchema.param("repo", Schema.String);
92const issueIdParam = HttpApiSchema.param("issueId", Schema.String);
93
94class WorkspaceSourceReposApi extends HttpApiGroup.make("repos")
95 .add(
96 HttpApiEndpoint.get("getRepo")`/repos/${ownerParam}/${repoParam}`
97 .addSuccess(Schema.Unknown),
98 )
99{}
100
101class WorkspaceSourceIssuesApi extends HttpApiGroup.make("issues")
102 .add(
103 HttpApiEndpoint.get("getIssue")`/issues/${issueIdParam}`
104 .addSuccess(Schema.Unknown),
105 )
106 .add(
107 HttpApiEndpoint.get("listIssues")`/issues`
108 .addSuccess(Schema.Unknown),
109 )
110{}
111
112class WorkspaceSourceReposRootApi extends HttpApi.make("workspaceSourceRepos")
113 .add(WorkspaceSourceReposApi)
114{}
115
116class WorkspaceSourceIssuesRootApi extends HttpApi.make("workspaceSourceIssues")
117 .add(WorkspaceSourceIssuesApi)
118{}
119
120const workspaceSourceReposLive = HttpApiBuilder.group(
121 WorkspaceSourceReposRootApi,
122 "repos",
123 (handlers) =>
124 handlers.handle("getRepo", ({ path }) =>
125 Effect.succeed({
126 full_name: `${path.owner}/${path.repo}`,
127 private: false,
128 }),
129 ),
130);
131
132const workspaceSourceIssuesLive = HttpApiBuilder.group(
133 WorkspaceSourceIssuesRootApi,
134 "issues",
135 (handlers) =>
136 handlers
137 .handle("getIssue", ({ path }) =>
138 Effect.succeed({
139 id: path.issueId,
140 title: `Issue ${path.issueId}`,
141 }),
142 )
143 .handle("listIssues", () =>
144 Effect.succeed([
145 {
146 id: "1",
147 title: "Issue 1",
148 },
149 ]),
150 ),
151);
152
153const workspaceSourceReposApiLayer = HttpApiBuilder.api(
154 WorkspaceSourceReposRootApi,
155).pipe(Layer.provide(workspaceSourceReposLive));
156
157const workspaceSourceIssuesApiLayer = HttpApiBuilder.api(
158 WorkspaceSourceIssuesRootApi,
159).pipe(Layer.provide(workspaceSourceIssuesLive));
160
161const createScopedExecutor = (input: {
162 services: LocalExecutorServices;
163 installation: LocalInstallation;
164}) =>
165 createExecutorEffect({
166 backend: createExecutorBackend({
167 loadServices: () =>
168 Effect.succeed({
169 ...input.services,
170 scope: {
171 ...input.services.scope,
172 actorScopeId: input.installation.actorScopeId,
173 resolutionScopeIds: input.installation.resolutionScopeIds,
174 },
175 storage: {
176 ...input.services.storage,
177 installation: {
178 load: () => Effect.succeed(input.installation),
179 getOrProvision: () => Effect.succeed(input.installation),
180 },
181 },
182 }),
183 }),
184 getLocalServerBaseUrl: () => "http://127.0.0.1",
185 });
186
187const makeSharedScopeExecutors = Effect.acquireRelease(
188 Effect.gen(function* () {
189 const fs = yield* FileSystem.FileSystem;
190 const sharedScopeId = ScopeIdSchema.make("org_shared_sdk_pair_test");
191
192 const workspaceRootA = yield* fs.makeTempDirectoryScoped({
193 prefix: "executor-sdk-scope-a-",
194 });
195 const workspaceRootB = yield* fs.makeTempDirectoryScoped({
196 prefix: "executor-sdk-scope-b-",
197 });
198
199 const servicesA = yield* createLocalExecutorServicesEffect({
200 workspaceRoot: workspaceRootA,
201 homeConfigPath: join(workspaceRootA, ".executor-home.jsonc"),
202 homeStateDirectory: join(workspaceRootA, ".executor-home-state"),
203 });
204 const servicesB = yield* createLocalExecutorServicesEffect({
205 workspaceRoot: workspaceRootB,
206 homeConfigPath: join(workspaceRootB, ".executor-home.jsonc"),
207 homeStateDirectory: join(workspaceRootB, ".executor-home-state"),
208 });
209
210 const baseInstallationA =
211 yield* servicesA.storage.installation.getOrProvision();
212 const baseInstallationB =
213 yield* servicesB.storage.installation.getOrProvision();
214
215 const sharedStorageClose = (() => {
216 let closed = false;
217 return async () => {
218 if (closed) {
219 return;
220 }
221 closed = true;
222 await servicesA.storage.close?.();
223 await servicesB.storage.close?.();
224 };
225 })();
226
227 const sharedStorage = {
228 executorState: servicesA.storage.executorState,
229 secretMaterial: servicesA.storage.secretMaterial,
230 close: sharedStorageClose,
231 };
232
233 const installationA: LocalInstallation = {
234 ...baseInstallationA,
235 actorScopeId: ScopeIdSchema.make("acc_sdk_executor_a"),
236 resolutionScopeIds: [
237 baseInstallationA.scopeId,
238 sharedScopeId,
239 ScopeIdSchema.make("acc_sdk_executor_a"),
240 ],
241 };
242 const installationB: LocalInstallation = {
243 ...baseInstallationB,
244 actorScopeId: ScopeIdSchema.make("acc_sdk_executor_b"),
245 resolutionScopeIds: [
246 baseInstallationB.scopeId,
247 sharedScopeId,
248 ScopeIdSchema.make("acc_sdk_executor_b"),
249 ],
250 };
251
252 const executorA = yield* createScopedExecutor({
253 services: {
254 ...servicesA,
255 storage: {
256 ...servicesA.storage,
257 ...sharedStorage,
258 },
259 },
260 installation: installationA,
261 });
262 const executorB = yield* createScopedExecutor({
263 services: {
264 ...servicesB,
265 storage: {
266 ...servicesB.storage,
267 ...sharedStorage,
268 },
269 },
270 installation: installationB,
271 });
272
273 return {
274 executorA,
275 executorB,
276 sharedScopeId,
277 };
278 }).pipe(Effect.provide(NodeFileSystem.layer)),
279 ({ executorA, executorB }) =>
280 Effect.promise(async () => {
281 await executorA.close();
282 await executorB.close();
283 }).pipe(Effect.orDie),
284);
285
286describe("executor", () => {
287 it.scoped(
288 "keeps workspace sources isolated across executors that share an ancestor scope",
289 () =>
290 Effect.gen(function* () {
291 const { executorA, executorB, sharedScopeId } =
292 yield* makeSharedScopeExecutors;
293 const openApiServer: OpenApiSpecServer = yield* makeOpenApiTestServer({
294 apiLayer: workspaceSourceReposApiLayer,
295 });
296
297 expect(executorA.resolutionScopeIds).toContain(sharedScopeId);
298 expect(executorB.resolutionScopeIds).toContain(sharedScopeId);
299
300 const sourceA = yield* withExecutorApiClient(executorA, (client) =>
301 client.sources.create({
302 path: {
303 workspaceId: executorA.scopeId,
304 },
305 payload: {
306 name: "Workspace A Source",
307 kind: "openapi",
308 endpoint: openApiServer.baseUrl,
309 namespace: "workspace.a",
310 binding: {
311 specUrl: openApiServer.specUrl,
312 defaultHeaders: null,
313 },
314 auth: { kind: "none" },
315 },
316 }),
317 );
318 const sourceB = yield* withExecutorApiClient(executorB, (client) =>
319 client.sources.create({
320 path: {
321 workspaceId: executorB.scopeId,
322 },
323 payload: {
324 name: "Workspace B Source",
325 kind: "openapi",
326 endpoint: openApiServer.baseUrl,
327 namespace: "workspace.b",
328 binding: {
329 specUrl: openApiServer.specUrl,
330 defaultHeaders: null,
331 },
332 auth: { kind: "none" },
333 },
334 }),
335 );
336
337 const listedA = yield* withExecutorApiClient(executorA, (client) =>
338 client.sources.list({
339 path: {
340 workspaceId: executorA.scopeId,
341 },
342 }),
343 );
344 const listedB = yield* withExecutorApiClient(executorB, (client) =>
345 client.sources.list({
346 path: {
347 workspaceId: executorB.scopeId,
348 },
349 }),
350 );
351
352 expect(listedA.map((source) => source.id)).toEqual([sourceA.id]);
353 expect(listedB.map((source) => source.id)).toEqual([sourceB.id]);
354
355 const lookupFromSibling = yield* expectLeft(
356 withExecutorApiClient(executorB, (client) =>
357 client.sources.get({
358 path: {
359 workspaceId: executorB.scopeId,
360 sourceId: sourceA.id,
361 },
362 }),
363 ),
364 );
365 assertInclude(String(lookupFromSibling), "Source not found");
366 }),
367 );
368
369 it.scoped(
370 "keeps each executor's tool surface tied to its own workspace sources",
371 () =>
372 Effect.gen(function* () {
373 const { executorA, executorB } =
374 yield* makeSharedScopeExecutors;
375 const openApiServerA: OpenApiSpecServer = yield* makeOpenApiTestServer({
376 apiLayer: workspaceSourceReposApiLayer,
377 });
378 const openApiServerB: OpenApiSpecServer = yield* makeOpenApiTestServer({
379 apiLayer: workspaceSourceIssuesApiLayer,
380 });
381
382 const sourceA = yield* withExecutorApiClient(executorA, (client) =>
383 client.sources.create({
384 path: {
385 workspaceId: executorA.scopeId,
386 },
387 payload: {
388 name: "Workspace A Tools",
389 kind: "openapi",
390 endpoint: openApiServerA.baseUrl,
391 namespace: "workspace.a",
392 binding: {
393 specUrl: openApiServerA.specUrl,
394 defaultHeaders: null,
395 },
396 auth: { kind: "none" },
397 },
398 }),
399 );
400 const sourceB = yield* withExecutorApiClient(executorB, (client) =>
401 client.sources.create({
402 path: {
403 workspaceId: executorB.scopeId,
404 },
405 payload: {
406 name: "Workspace B Tools",
407 kind: "openapi",
408 endpoint: openApiServerB.baseUrl,
409 namespace: "workspace.b",
410 binding: {
411 specUrl: openApiServerB.specUrl,
412 defaultHeaders: null,
413 },
414 auth: { kind: "none" },
415 },
416 }),
417 );
418
419 const inspectionA = yield* withExecutorApiClient(executorA, (client) =>
420 client.sources.inspection({
421 path: {
422 workspaceId: executorA.scopeId,
423 sourceId: sourceA.id,
424 },
425 }),
426 );
427 const inspectionB = yield* withExecutorApiClient(executorB, (client) =>
428 client.sources.inspection({
429 path: {
430 workspaceId: executorB.scopeId,
431 sourceId: sourceB.id,
432 },
433 }),
434 );
435
436 expect(inspectionA.toolCount).toBe(1);
437 expect(inspectionB.toolCount).toBe(2);
438
439 const siblingInspection = yield* expectLeft(
440 withExecutorApiClient(executorA, (client) =>
441 client.sources.inspection({
442 path: {
443 workspaceId: executorA.scopeId,
444 sourceId: sourceB.id,
445 },
446 }),
447 ),
448 );
449 assertInclude(String(siblingInspection), "Source not found");
450 }),
451 60_000,
452 );
453});
packages/platform/sdk/src/executor.ts+95−78
@@ -1,19 +1,23 @@
1import * as Effect from "effect/Effect";1import * as Effect from "effect/Effect";
2 2
3import type {3import type {
4 AccountId,4 ScopeId,
5 Execution,5 Execution,
6 ExecutionEnvelope,6 ExecutionEnvelope,
7 ExecutionInteraction,7 ExecutionInteraction,
8 LocalInstallation,8 LocalInstallation,
9 LocalWorkspacePolicy,9 LocalScopePolicy,
10 ProviderAuthGrant,10 ProviderAuthGrant,
11 Source,11 Source,
12 WorkspaceId,12 ScopeOauthClient,
13 WorkspaceOauthClient,13} from "./schema";
14import {
15 ExecutionIdSchema,
14} from "./schema";16} from "./schema";
15import { ExecutionIdSchema } from "./schema";17import type {
16import type { CreateExecutionPayload, ResumeExecutionPayload } from "./executions/contracts";18 CreateExecutionPayload,
19 ResumeExecutionPayload,
20} from "./executions/contracts";
17import type {21import type {
18 CreateSecretPayload,22 CreateSecretPayload,
19 CreateSecretResult,23 CreateSecretResult,
@@ -36,7 +40,10 @@ import { · 16 unmodified lines
36 listLocalSecrets,40 listLocalSecrets,
37 updateLocalSecret,41 updateLocalSecret,
38} from "./local/secrets";42} from "./local/secrets";
39import type { CreatePolicyPayload, UpdatePolicyPayload } from "./policies/contracts";43import type {
44 CreatePolicyPayload,
45 UpdatePolicyPayload,
46} from "./policies/contracts";
40import {47import {
41 createPolicy,48 createPolicy,
42 getPolicy,49 getPolicy,
@@ -46,10 +53,12 @@ import { · 3 unmodified lines
46} from "./policies/operations";53} from "./policies/operations";
47import type {54import type {
48 CreateSourcePayload,55 CreateSourcePayload,
49 CreateWorkspaceOauthClientPayload,56 CreateScopeOauthClientPayload,
50 UpdateSourcePayload,57 UpdateSourcePayload,
51} from "./sources/contracts";58} from "./sources/contracts";
52import { discoverSource } from "./sources/discovery";59import {
60 discoverSource,
61} from "./sources/discovery";
53import {62import {
54 discoverSourceInspectionTools,63 discoverSourceInspectionTools,
55 getSourceInspection,64 getSourceInspection,
@@ -62,17 +71,23 @@ import { · 6 unmodified lines
62 removeSource,71 removeSource,
63 updateSource,72 updateSource,
64} from "./sources/operations";73} from "./sources/operations";
65import type { ExecutorBackend } from "./backend";74import type {
75 ExecutorBackend,
76} from "./backend";
66import {77import {
67 provideExecutorRuntime,78 provideExecutorRuntime,
68 type ExecutorRuntime,79 type ExecutorRuntime,
69 type ExecutorRuntimeOptions,80 type ExecutorRuntimeOptions,
70 type CreateWorkspaceInternalToolMap,81 type CreateScopeInternalToolMap,
71 type ResolveExecutionEnvironment,82 type ResolveExecutionEnvironment,
72 type ResolveSecretMaterial,83 type ResolveSecretMaterial,
73 RuntimeSourceAuthServiceTag,84 RuntimeSourceAuthServiceTag,
74} from "./runtime";85} from "./runtime";
75import { createExecution, getExecution, resumeExecution } from "./runtime/execution/service";86import {
87 createExecution,
88 getExecution,
89 resumeExecution,
90} from "./runtime/execution/service";
76import type {91import type {
77 CompleteProviderOauthCallbackResult,92 CompleteProviderOauthCallbackResult,
78 CompleteSourceCredentialSetupResult,93 CompleteSourceCredentialSetupResult,
@@ -96,29 +111,30 @@ type ProvidedEffect<T extends Effect.Effect<any, any, any>> = Effect.Effect< · 17 unmodified lines
96 111
97export type ExecutorSourceInput = DistributiveOmit<112export type ExecutorSourceInput = DistributiveOmit<
98 ExecutorAddSourceInput,113 ExecutorAddSourceInput,
99 "workspaceId" | "actorAccountId" | "executionId" | "interactionId"114 "scopeId" | "actorScopeId" | "executionId" | "interactionId"
100>;115>;
101 116
102export type ExecutorSourceBatchInput = DistributiveOmit<117export type ExecutorSourceBatchInput = DistributiveOmit<
103 ConnectGoogleDiscoveryBatchInput,118 ConnectGoogleDiscoveryBatchInput,
104 "workspaceId" | "actorAccountId" | "executionId" | "interactionId"119 "scopeId" | "actorScopeId" | "executionId" | "interactionId"
105>;120>;
106 121
107export type ExecutorMcpSourceInput = DistributiveOmit<122export type ExecutorMcpSourceInput = DistributiveOmit<
108 ConnectMcpSourceInput,123 ConnectMcpSourceInput,
109 "workspaceId" | "actorAccountId"124 "scopeId" | "actorScopeId"
110>;125>;
111 126
112export type ExecutorSourceOAuthInput = DistributiveOmit<127export type ExecutorSourceOAuthInput = DistributiveOmit<
113 StartSourceOAuthSessionInput,128 StartSourceOAuthSessionInput,
114 "workspaceId" | "actorAccountId"129 "scopeId" | "actorScopeId"
115>;130>;
116 131
117export type Executor = {132export type Executor = {
118 runtime: ExecutorRuntime;133 runtime: ExecutorRuntime;
119 installation: LocalInstallation;134 installation: LocalInstallation;
120 workspaceId: WorkspaceId;135 scopeId: ScopeId;
121 accountId: AccountId;136 actorScopeId: ScopeId;
137 resolutionScopeIds: ReadonlyArray<ScopeId>;
122 provide: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, any>;138 provide: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, any>;
123 run: <A, E, R>(effect: Effect.Effect<A, E, R>) => Promise<A>;139 run: <A, E, R>(effect: Effect.Effect<A, E, R>) => Promise<A>;
124 close: () => Promise<void>;140 close: () => Promise<void>;
@@ -202,12 +218,12 @@ export type Executor = { · 77 unmodified lines
202 oauthClients: {218 oauthClients: {
203 list: (219 list: (
204 providerKey: string,220 providerKey: string,
205 ) => Effect.Effect<ReadonlyArray<WorkspaceOauthClient>, Error, never>;221 ) => Effect.Effect<ReadonlyArray<ScopeOauthClient>, Error, never>;
206 create: (222 create: (
207 payload: CreateWorkspaceOauthClientPayload,223 payload: CreateScopeOauthClientPayload,
208 ) => Effect.Effect<WorkspaceOauthClient, Error, never>;224 ) => Effect.Effect<ScopeOauthClient, Error, never>;
209 remove: (225 remove: (
210 oauthClientId: WorkspaceOauthClient["id"],226 oauthClientId: ScopeOauthClient["id"],
211 ) => Effect.Effect<boolean, Error, never>;227 ) => Effect.Effect<boolean, Error, never>;
212 };228 };
213 providerGrants: {229 providerGrants: {
@@ -225,8 +241,8 @@ export type Executor = { · 11 unmodified lines
225 errorDescription?: string | null;241 errorDescription?: string | null;
226 }) => Effect.Effect<CompleteSourceOAuthSessionResult, Error, never>;242 }) => Effect.Effect<CompleteSourceOAuthSessionResult, Error, never>;
227 completeProviderCallback: (input: {243 completeProviderCallback: (input: {
228 workspaceId?: WorkspaceId;244 scopeId?: ScopeId;
229 actorAccountId?: AccountId | null;245 actorScopeId?: ScopeId | null;
230 state: string;246 state: string;
231 code?: string | null;247 code?: string | null;
232 error?: string | null;248 error?: string | null;
@@ -275,13 +291,13 @@ export type Executor = { · 42 unmodified lines
275 remove: (secretId: string) => Promise<DeleteSecretResult>;291 remove: (secretId: string) => Promise<DeleteSecretResult>;
276 };292 };
277 policies: {293 policies: {
278 list: () => Promise<ReadonlyArray<LocalWorkspacePolicy>>;294 list: () => Promise<ReadonlyArray<LocalScopePolicy>>;
279 create: (payload: CreatePolicyPayload) => Promise<LocalWorkspacePolicy>;295 create: (payload: CreatePolicyPayload) => Promise<LocalScopePolicy>;
280 get: (policyId: string) => Promise<LocalWorkspacePolicy>;296 get: (policyId: string) => Promise<LocalScopePolicy>;
281 update: (297 update: (
282 policyId: string,298 policyId: string,
283 payload: UpdatePolicyPayload,299 payload: UpdatePolicyPayload,
284 ) => Promise<LocalWorkspacePolicy>;300 ) => Promise<LocalScopePolicy>;
285 remove: (policyId: string) => Promise<boolean>;301 remove: (policyId: string) => Promise<boolean>;
286 };302 };
287 sources: {303 sources: {
@@ -314,11 +330,11 @@ export type Executor = { · 26 unmodified lines
314 }) => Promise<Effect.Effect.Success<ReturnType<typeof discoverSourceInspectionTools>>>;330 }) => Promise<Effect.Effect.Success<ReturnType<typeof discoverSourceInspectionTools>>>;
315 };331 };
316 oauthClients: {332 oauthClients: {
317 list: (providerKey: string) => Promise<ReadonlyArray<WorkspaceOauthClient>>;333 list: (providerKey: string) => Promise<ReadonlyArray<ScopeOauthClient>>;
318 create: (334 create: (
319 payload: CreateWorkspaceOauthClientPayload,335 payload: CreateScopeOauthClientPayload,
320 ) => Promise<WorkspaceOauthClient>;336 ) => Promise<ScopeOauthClient>;
321 remove: (oauthClientId: WorkspaceOauthClient["id"]) => Promise<boolean>;337 remove: (oauthClientId: ScopeOauthClient["id"]) => Promise<boolean>;
322 };338 };
323 providerGrants: {339 providerGrants: {
324 remove: (grantId: ProviderAuthGrant["id"]) => Promise<boolean>;340 remove: (grantId: ProviderAuthGrant["id"]) => Promise<boolean>;
@@ -333,8 +349,8 @@ export type Executor = { · 8 unmodified lines
333 errorDescription?: string | null;349 errorDescription?: string | null;
334 }) => Promise<CompleteSourceOAuthSessionResult>;350 }) => Promise<CompleteSourceOAuthSessionResult>;
335 completeProviderCallback: (input: {351 completeProviderCallback: (input: {
336 workspaceId?: WorkspaceId;352 scopeId?: ScopeId;
337 actorAccountId?: AccountId | null;353 actorScopeId?: ScopeId | null;
338 state: string;354 state: string;
339 code?: string | null;355 code?: string | null;
340 error?: string | null;356 error?: string | null;
@@ -357,8 +373,8 @@ export type CreateExecutorOptions = ExecutorRuntimeOptions & { · 16 unmodified lines
357 373
358const fromRuntime = (runtime: ExecutorRuntime): Executor => {374const fromRuntime = (runtime: ExecutorRuntime): Executor => {
359 const installation = runtime.localInstallation;375 const installation = runtime.localInstallation;
360 const workspaceId = installation.workspaceId;376 const scopeId = installation.scopeId;
361 const accountId = installation.accountId;377 const actorScopeId = installation.actorScopeId;
362 const provide = <A, E, R>(effect: Effect.Effect<A, E, R>) =>378 const provide = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
363 provideExecutorRuntime(effect, runtime);379 provideExecutorRuntime(effect, runtime);
364 const run = <A, E, R>(effect: Effect.Effect<A, E, R>) =>380 const run = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
@@ -383,7 +399,7 @@ const fromRuntime = (runtime: ExecutorRuntime): Executor => { · 18 unmodified lines
383 get: ({ sourceId, interactionId }) =>399 get: ({ sourceId, interactionId }) =>
384 provide(400 provide(
385 getSourceCredentialInteraction({401 getSourceCredentialInteraction({
386 workspaceId,402 scopeId,
387 sourceId,403 sourceId,
388 interactionId,404 interactionId,
389 }),405 }),
@@ -391,7 +407,7 @@ const fromRuntime = (runtime: ExecutorRuntime): Executor => { · 1 unmodified line
391 submit: ({ sourceId, interactionId, action, token }) =>407 submit: ({ sourceId, interactionId, action, token }) =>
392 provide(408 provide(
393 submitSourceCredentialInteraction({409 submitSourceCredentialInteraction({
394 workspaceId,410 scopeId,
395 sourceId,411 sourceId,
396 interactionId,412 interactionId,
397 action,413 action,
@@ -401,7 +417,7 @@ const fromRuntime = (runtime: ExecutorRuntime): Executor => { · 3 unmodified lines
401 complete: ({ sourceId, state, code, error, errorDescription }) =>417 complete: ({ sourceId, state, code, error, errorDescription }) =>
402 provide(418 provide(
403 completeSourceCredentialSetup({419 completeSourceCredentialSetup({
404 workspaceId,420 scopeId,
405 sourceId,421 sourceId,
406 state,422 state,
407 code,423 code,
@@ -419,15 +435,15 @@ const fromRuntime = (runtime: ExecutorRuntime): Executor => { · 11 unmodified lines
419 remove: (secretId: string) => provide(deleteLocalSecret(secretId)),435 remove: (secretId: string) => provide(deleteLocalSecret(secretId)),
420 },436 },
421 policies: {437 policies: {
422 list: () => provide(listPolicies(workspaceId)),438 list: () => provide(listPolicies(scopeId)),
423 create: (payload: CreatePolicyPayload) =>439 create: (payload: CreatePolicyPayload) =>
424 provide(createPolicy({ workspaceId, payload })),440 provide(createPolicy({ scopeId, payload })),
425 get: (policyId: string) =>441 get: (policyId: string) =>
426 provide(getPolicy({ workspaceId, policyId: policyId as never })),442 provide(getPolicy({ scopeId, policyId: policyId as never })),
427 update: (policyId: string, payload: UpdatePolicyPayload) =>443 update: (policyId: string, payload: UpdatePolicyPayload) =>
428 provide(updatePolicy({ workspaceId, policyId: policyId as never, payload })),444 provide(updatePolicy({ scopeId, policyId: policyId as never, payload })),
429 remove: (policyId: string) =>445 remove: (policyId: string) =>
430 provide(removePolicy({ workspaceId, policyId: policyId as never })),446 provide(removePolicy({ scopeId, policyId: policyId as never })),
431 },447 },
432 sources: {448 sources: {
433 add: (input: ExecutorSourceInput, options?: { baseUrl?: string | null }) =>449 add: (input: ExecutorSourceInput, options?: { baseUrl?: string | null }) =>
@@ -436,8 +452,8 @@ const fromRuntime = (runtime: ExecutorRuntime): Executor => { · 2 unmodified lines
436 return service.addExecutorSource(452 return service.addExecutorSource(
437 {453 {
438 ...input,454 ...input,
439 workspaceId,455 scopeId,
440 actorAccountId: accountId,456 actorScopeId: actorScopeId,
441 executionId: session.executionId,457 executionId: session.executionId,
442 interactionId: session.interactionId,458 interactionId: session.interactionId,
443 },459 },
@@ -448,8 +464,8 @@ const fromRuntime = (runtime: ExecutorRuntime): Executor => { · 4 unmodified lines
448 provideSourceAuth((service) =>464 provideSourceAuth((service) =>
449 service.connectMcpSource({465 service.connectMcpSource({
450 ...payload,466 ...payload,
451 workspaceId,467 scopeId,
452 actorAccountId: accountId,468 actorScopeId: actorScopeId,
453 }),469 }),
454 ),470 ),
455 connectBatch: (payload: ExecutorSourceBatchInput) =>471 connectBatch: (payload: ExecutorSourceBatchInput) =>
@@ -457,8 +473,8 @@ const fromRuntime = (runtime: ExecutorRuntime): Executor => { · 1 unmodified line
457 const session = createSdkSourceSession();473 const session = createSdkSourceSession();
458 return service.connectGoogleDiscoveryBatch({474 return service.connectGoogleDiscoveryBatch({
459 ...payload,475 ...payload,
460 workspaceId,476 scopeId,
461 actorAccountId: accountId,477 actorScopeId: actorScopeId,
462 executionId: session.executionId,478 executionId: session.executionId,
463 interactionId: session.interactionId,479 interactionId: session.interactionId,
464 });480 });
@@ -467,22 +483,22 @@ const fromRuntime = (runtime: ExecutorRuntime): Executor => { · 2 unmodified lines
467 url: string;483 url: string;
468 probeAuth?: Parameters<typeof discoverSource>[0]["probeAuth"];484 probeAuth?: Parameters<typeof discoverSource>[0]["probeAuth"];
469 }) => provide(discoverSource(input)),485 }) => provide(discoverSource(input)),
470 list: () => provide(listSources({ workspaceId, accountId })),486 list: () => provide(listSources({ scopeId, actorScopeId })),
471 create: (payload: CreateSourcePayload) =>487 create: (payload: CreateSourcePayload) =>
472 provide(createSource({ workspaceId, accountId, payload })),488 provide(createSource({ scopeId, actorScopeId, payload })),
473 get: (sourceId: Source["id"]) =>489 get: (sourceId: Source["id"]) =>
474 provide(getSource({ workspaceId, sourceId, accountId })),490 provide(getSource({ scopeId, sourceId, actorScopeId })),
475 update: (sourceId: Source["id"], payload: UpdateSourcePayload) =>491 update: (sourceId: Source["id"], payload: UpdateSourcePayload) =>
476 provide(updateSource({ workspaceId, sourceId, accountId, payload })),492 provide(updateSource({ scopeId, sourceId, actorScopeId, payload })),
477 remove: (sourceId: Source["id"]) =>493 remove: (sourceId: Source["id"]) =>
478 provide(removeSource({ workspaceId, sourceId })),494 provide(removeSource({ scopeId, sourceId })),
479 inspection: {495 inspection: {
480 get: (sourceId: Source["id"]) =>496 get: (sourceId: Source["id"]) =>
481 provide(getSourceInspection({ workspaceId, sourceId })),497 provide(getSourceInspection({ scopeId, sourceId })),
482 tool: ({ sourceId, toolPath }: { sourceId: Source["id"]; toolPath: string }) =>498 tool: ({ sourceId, toolPath }: { sourceId: Source["id"]; toolPath: string }) =>
483 provide(499 provide(
484 getSourceInspectionToolDetail({500 getSourceInspectionToolDetail({
485 workspaceId,501 scopeId,
486 sourceId,502 sourceId,
487 toolPath,503 toolPath,
488 }),504 }),
@@ -496,7 +512,7 @@ const fromRuntime = (runtime: ExecutorRuntime): Executor => { · 7 unmodified lines
496 }) =>512 }) =>
497 provide(513 provide(
498 discoverSourceInspectionTools({514 discoverSourceInspectionTools({
499 workspaceId,515 scopeId,
500 sourceId,516 sourceId,
501 payload,517 payload,
502 }),518 }),
@@ -505,24 +521,24 @@ const fromRuntime = (runtime: ExecutorRuntime): Executor => { · 2 unmodified lines
505 oauthClients: {521 oauthClients: {
506 list: (providerKey: string) =>522 list: (providerKey: string) =>
507 provideSourceAuth((service) =>523 provideSourceAuth((service) =>
508 service.listWorkspaceOauthClients({524 service.listScopeOauthClients({
509 workspaceId,525 scopeId,
510 providerKey,526 providerKey,
511 }),527 }),
512 ),528 ),
513 create: (payload: CreateWorkspaceOauthClientPayload) =>529 create: (payload: CreateScopeOauthClientPayload) =>
514 provideSourceAuth((service) =>530 provideSourceAuth((service) =>
515 service.createWorkspaceOauthClient({531 service.createScopeOauthClient({
516 workspaceId,532 scopeId,
517 providerKey: payload.providerKey,533 providerKey: payload.providerKey,
518 label: payload.label,534 label: payload.label,
519 oauthClient: payload.oauthClient,535 oauthClient: payload.oauthClient,
520 }),536 }),
521 ),537 ),
522 remove: (oauthClientId: WorkspaceOauthClient["id"]) =>538 remove: (oauthClientId: ScopeOauthClient["id"]) =>
523 provideSourceAuth((service) =>539 provideSourceAuth((service) =>
524 service.removeWorkspaceOauthClient({540 service.removeScopeOauthClient({
525 workspaceId,541 scopeId,
526 oauthClientId,542 oauthClientId,
527 }),543 }),
528 ),544 ),
@@ -531,7 +547,7 @@ const fromRuntime = (runtime: ExecutorRuntime): Executor => { · 2 unmodified lines
531 remove: (grantId: ProviderAuthGrant["id"]) =>547 remove: (grantId: ProviderAuthGrant["id"]) =>
532 provideSourceAuth((service) =>548 provideSourceAuth((service) =>
533 service.removeProviderAuthGrant({549 service.removeProviderAuthGrant({
534 workspaceId,550 scopeId,
535 grantId,551 grantId,
536 }),552 }),
537 ),553 ),
@@ -542,8 +558,8 @@ const fromRuntime = (runtime: ExecutorRuntime): Executor => { · 4 unmodified lines
542 provideSourceAuth((service) =>558 provideSourceAuth((service) =>
543 service.startSourceOAuthSession({559 service.startSourceOAuthSession({
544 ...input,560 ...input,
545 workspaceId,561 scopeId,
546 actorAccountId: accountId,562 actorScopeId: actorScopeId,
547 }),563 }),
548 ),564 ),
549 completeSourceAuth: ({ state, code, error, errorDescription }) =>565 completeSourceAuth: ({ state, code, error, errorDescription }) =>
@@ -559,8 +575,8 @@ const fromRuntime = (runtime: ExecutorRuntime): Executor => { · 9 unmodified lines
559 provideSourceAuth((service) =>575 provideSourceAuth((service) =>
560 service.completeProviderOauthCallback({576 service.completeProviderOauthCallback({
561 ...input,577 ...input,
562 workspaceId: input.workspaceId ?? workspaceId,578 scopeId: input.scopeId ?? scopeId,
563 actorAccountId: input.actorAccountId ?? accountId,579 actorScopeId: input.actorScopeId ?? actorScopeId,
564 }),580 }),
565 ),581 ),
566 },582 },
@@ -568,20 +584,20 @@ const fromRuntime = (runtime: ExecutorRuntime): Executor => { · 1 unmodified line
568 create: (payload: CreateExecutionPayload) =>584 create: (payload: CreateExecutionPayload) =>
569 provide(585 provide(
570 createExecution({586 createExecution({
571 workspaceId,587 scopeId,
572 payload,588 payload,
573 createdByAccountId: accountId,589 createdByScopeId: actorScopeId,
574 }),590 }),
575 ),591 ),
576 get: (executionId: Execution["id"]) =>592 get: (executionId: Execution["id"]) =>
577 provide(getExecution({ workspaceId, executionId })),593 provide(getExecution({ scopeId, executionId })),
578 resume: (executionId: Execution["id"], payload: ResumeExecutionPayload) =>594 resume: (executionId: Execution["id"], payload: ResumeExecutionPayload) =>
579 provide(595 provide(
580 resumeExecution({596 resumeExecution({
581 workspaceId,597 scopeId,
582 executionId,598 executionId,
583 payload,599 payload,
584 resumedByAccountId: accountId,600 resumedByScopeId: actorScopeId,
585 }),601 }),
586 ),602 ),
587 },603 },
@@ -590,8 +606,9 @@ const fromRuntime = (runtime: ExecutorRuntime): Executor => { · 2 unmodified lines
590 return {606 return {
591 runtime,607 runtime,
592 installation,608 installation,
593 workspaceId,609 scopeId,
594 accountId,610 actorScopeId,
611 resolutionScopeIds: installation.resolutionScopeIds,
595 provide,612 provide,
596 run,613 run,
597 close: () => runtime.close(),614 close: () => runtime.close(),
packages/platform/sdk/src/local/contracts.ts+3−1
@@ -1,4 +1,6 @@
1import { SecretMaterialPurposeSchema } from "../schema";1import {
2 SecretMaterialPurposeSchema,
3} from "../schema";
2import * as Schema from "effect/Schema";4import * as Schema from "effect/Schema";
3 5
4export const SecretProviderSchema = Schema.Struct({6export const SecretProviderSchema = Schema.Struct({
packages/platform/sdk/src/local/operations.ts+32−20
@@ -2,25 +2,37 @@ import type { · 1 unmodified line
2 Execution,2 Execution,
3 ExecutionInteraction,3 ExecutionInteraction,
4 Source,4 Source,
5 WorkspaceId,5 ScopeId,
6} from "../schema";6} from "../schema";
7import {7import {
8 SourceIdSchema,8 SourceIdSchema,
9 WorkspaceIdSchema,9 ScopeIdSchema,
10} from "../schema";10} from "../schema";
11import * as Effect from "effect/Effect";11import * as Effect from "effect/Effect";
12import * as Option from "effect/Option";12import * as Option from "effect/Option";
13 13
14import { submitExecutionInteractionResponse } from "../runtime/execution/service";14import {
15import { LiveExecutionManagerService } from "../runtime/execution/live";15 submitExecutionInteractionResponse,
16import { operationErrors } from "../runtime/policy/operation-errors";16} from "../runtime/execution/service";
17import { InstallationStore } from "../runtime/workspace/storage";17import {
18import { RuntimeSourceAuthServiceTag } from "../runtime/sources/source-auth-service";18 LiveExecutionManagerService,
19} from "../runtime/execution/live";
20import {
21 operationErrors,
22} from "../runtime/policy/operation-errors";
23import {
24 InstallationStore,
25} from "../runtime/scope/storage";
26import {
27 RuntimeSourceAuthServiceTag,
28} from "../runtime/sources/source-auth-service";
19import {29import {
20 createSourceCredentialSelectionBearerContent,30 createSourceCredentialSelectionBearerContent,
21 createSourceCredentialSelectionNoneContent,31 createSourceCredentialSelectionNoneContent,
22} from "../runtime/sources/source-credential-interactions";32} from "../runtime/sources/source-credential-interactions";
23import { ExecutorStateStore } from "../runtime/executor-state-store";33import {
34 ExecutorStateStore,
35} from "../runtime/executor-state-store";
24 36
25const localOps = {37const localOps = {
26 installation: operationErrors("local.installation.get"),38 installation: operationErrors("local.installation.get"),
@@ -34,7 +46,7 @@ type SourceCredentialInteraction = { · 7 unmodified lines
34 executionId: Execution["id"];46 executionId: Execution["id"];
35 status: ExecutionInteraction["status"];47 status: ExecutionInteraction["status"];
36 message: string;48 message: string;
37 workspaceId: WorkspaceId;49 scopeId: ScopeId;
38 sourceId: Source["id"];50 sourceId: Source["id"];
39 sourceLabel: string;51 sourceLabel: string;
40 endpoint: string;52 endpoint: string;
@@ -100,10 +112,10 @@ export const decodeSourceCredentialInteraction = ( · 59 unmodified lines
100 return null;112 return null;
101 }113 }
102 114
103 const workspaceId = trimOrNull(asString(args.workspaceId));115 const scopeId = trimOrNull(asString(args.scopeId));
104 const sourceId = trimOrNull(asString(args.sourceId));116 const sourceId = trimOrNull(asString(args.sourceId));
105 const message = trimOrNull(asString(elicitation.message));117 const message = trimOrNull(asString(elicitation.message));
106 if (workspaceId === null || sourceId === null || message === null) {118 if (scopeId === null || sourceId === null || message === null) {
107 return null;119 return null;
108 }120 }
109 121
@@ -112,7 +124,7 @@ export const decodeSourceCredentialInteraction = ( · 2 unmodified lines
112 executionId: interaction.executionId,124 executionId: interaction.executionId,
113 status: interaction.status,125 status: interaction.status,
114 message,126 message,
115 workspaceId: WorkspaceIdSchema.make(workspaceId),127 scopeId: ScopeIdSchema.make(scopeId),
116 sourceId: SourceIdSchema.make(sourceId),128 sourceId: SourceIdSchema.make(sourceId),
117 };129 };
118 } catch {130 } catch {
@@ -121,7 +133,7 @@ export const decodeSourceCredentialInteraction = ( · 2 unmodified lines
121};133};
122 134
123const loadSourceCredentialInteraction = (input: {135const loadSourceCredentialInteraction = (input: {
124 workspaceId: WorkspaceId;136 scopeId: ScopeId;
125 sourceId: Source["id"];137 sourceId: Source["id"];
126 interactionId: ExecutionInteraction["id"];138 interactionId: ExecutionInteraction["id"];
127 operation:139 operation:
@@ -153,18 +165,18 @@ const loadSourceCredentialInteraction = (input: { · 25 unmodified lines
153 const decoded = decodeSourceCredentialInteraction(stored.value);165 const decoded = decodeSourceCredentialInteraction(stored.value);
154 if (166 if (
155 decoded === null ||167 decoded === null ||
156 decoded.workspaceId !== input.workspaceId ||168 decoded.scopeId !== input.scopeId ||
157 decoded.sourceId !== input.sourceId169 decoded.sourceId !== input.sourceId
158 ) {170 ) {
159 return yield* input.operation.notFound(171 return yield* input.operation.notFound(
160 "Source credential request not found",172 "Source credential request not found",
161 `workspaceId=${input.workspaceId} sourceId=${input.sourceId} interactionId=${input.interactionId}`,173 `scopeId=${input.scopeId} sourceId=${input.sourceId} interactionId=${input.interactionId}`,
162 );174 );
163 }175 }
164 176
165 const source = yield* sourceAuthService177 const source = yield* sourceAuthService
166 .getSourceById({178 .getSourceById({
167 workspaceId: input.workspaceId,179 scopeId: input.scopeId,
168 sourceId: input.sourceId,180 sourceId: input.sourceId,
169 })181 })
170 .pipe(182 .pipe(
@@ -197,7 +209,7 @@ export const getLocalInstallation = () => · 26 unmodified lines
197 });209 });
198 210
199export const getSourceCredentialInteraction = (input: {211export const getSourceCredentialInteraction = (input: {
200 workspaceId: WorkspaceId;212 scopeId: ScopeId;
201 sourceId: Source["id"];213 sourceId: Source["id"];
202 interactionId: ExecutionInteraction["id"];214 interactionId: ExecutionInteraction["id"];
203}) =>215}) =>
@@ -207,7 +219,7 @@ export const getSourceCredentialInteraction = (input: { · 3 unmodified lines
207 });219 });
208 220
209export const submitSourceCredentialInteraction = (input: {221export const submitSourceCredentialInteraction = (input: {
210 workspaceId: WorkspaceId;222 scopeId: ScopeId;
211 sourceId: Source["id"];223 sourceId: Source["id"];
212 interactionId: ExecutionInteraction["id"];224 interactionId: ExecutionInteraction["id"];
213 action: "submit" | "continue" | "cancel";225 action: "submit" | "continue" | "cancel";
@@ -215,7 +227,7 @@ export const submitSourceCredentialInteraction = (input: { · 1 unmodified line
215}) =>227}) =>
216 Effect.gen(function* () {228 Effect.gen(function* () {
217 const interaction = yield* loadSourceCredentialInteraction({229 const interaction = yield* loadSourceCredentialInteraction({
218 workspaceId: input.workspaceId,230 scopeId: input.scopeId,
219 sourceId: input.sourceId,231 sourceId: input.sourceId,
220 interactionId: input.interactionId,232 interactionId: input.interactionId,
221 operation: localOps.sourceCredentialSubmit,233 operation: localOps.sourceCredentialSubmit,
@@ -370,7 +382,7 @@ export const submitSourceCredentialInteraction = (input: { · 148 unmodified lines
370 });382 });
371 383
372export const completeSourceCredentialSetup = (input: {384export const completeSourceCredentialSetup = (input: {
373 workspaceId: WorkspaceId;385 scopeId: ScopeId;
374 sourceId: Source["id"];386 sourceId: Source["id"];
375 state: string;387 state: string;
376 code?: string | null;388 code?: string | null;
packages/platform/sdk/src/local/secrets.ts+18−10
@@ -1,7 +1,9 @@
1import * as Effect from "effect/Effect";1import * as Effect from "effect/Effect";
2import * as Option from "effect/Option";2import * as Option from "effect/Option";
3 3
4import { SecretMaterialIdSchema } from "../schema";4import {
5 SecretMaterialIdSchema,
6} from "../schema";
5import {7import {
6 type CreateSecretPayload,8 type CreateSecretPayload,
7 type CreateSecretResult,9 type CreateSecretResult,
@@ -16,15 +18,21 @@ import { · 8 unmodified lines
16 ControlPlaneNotFoundError,18 ControlPlaneNotFoundError,
17 ControlPlaneStorageError,19 ControlPlaneStorageError,
18} from "../errors";20} from "../errors";
19import { requireRuntimeLocalWorkspace } from "../runtime/workspace/runtime-context";21import {
22 requireRuntimeLocalScope,
23} from "../runtime/scope/runtime-context";
20import {24import {
21 LocalInstanceConfigService,25 LocalInstanceConfigService,
22 SecretMaterialDeleterService,26 SecretMaterialDeleterService,
23 SecretMaterialStorerService,27 SecretMaterialStorerService,
24 SecretMaterialUpdaterService,28 SecretMaterialUpdaterService,
25} from "../runtime/workspace/secret-material-providers";29} from "../runtime/scope/secret-material-providers";
26import { RuntimeSourceStoreService } from "../runtime/sources/source-store";30import {
27import { ExecutorStateStore } from "../runtime/executor-state-store";31 RuntimeSourceStoreService,
32} from "../runtime/sources/source-store";
33import {
34 ExecutorStateStore,
35} from "../runtime/executor-state-store";
28 36
29const secretStorageError = (operation: string, message: string) =>37const secretStorageError = (operation: string, message: string) =>
30 new ControlPlaneStorageError({38 new ControlPlaneStorageError({
@@ -40,9 +48,9 @@ export const listLocalSecrets = () => · 9 unmodified lines
40 Effect.gen(function* () {48 Effect.gen(function* () {
41 const store = yield* ExecutorStateStore;49 const store = yield* ExecutorStateStore;
42 const sourceStore = yield* RuntimeSourceStoreService;50 const sourceStore = yield* RuntimeSourceStoreService;
43 const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace().pipe(51 const runtimeLocalScope = yield* requireRuntimeLocalScope().pipe(
44 Effect.mapError(() =>52 Effect.mapError(() =>
45 secretStorageError("secrets.list", "Failed resolving local workspace."),53 secretStorageError("secrets.list", "Failed resolving local scope."),
46 ),54 ),
47 );55 );
48 const secretMaterials = yield* store.secretMaterials56 const secretMaterials = yield* store.secretMaterials
@@ -53,10 +61,10 @@ export const listLocalSecrets = () => · 4 unmodified lines
53 ),61 ),
54 );62 );
55 const linkedSourcesMap = yield* sourceStore63 const linkedSourcesMap = yield* sourceStore
56 .listLinkedSecretSourcesInWorkspace(64 .listLinkedSecretSourcesInScope(
57 runtimeLocalWorkspace.installation.workspaceId,65 runtimeLocalScope.installation.scopeId,
58 {66 {
59 actorAccountId: runtimeLocalWorkspace.installation.accountId,67 actorScopeId: runtimeLocalScope.installation.actorScopeId,
60 },68 },
61 )69 )
62 .pipe(70 .pipe(
packages/platform/sdk/src/policies/contracts.ts+10−8
@@ -1,23 +1,25 @@
1import {1import {
2 LocalWorkspacePolicyApprovalModeSchema,2 LocalScopePolicyApprovalModeSchema,
3 LocalWorkspacePolicyEffectSchema,3 LocalScopePolicyEffectSchema,
4} from "../schema";4} from "../schema";
5import * as Schema from "effect/Schema";5import * as Schema from "effect/Schema";
6 6
7import { OptionalTrimmedNonEmptyStringSchema } from "../string-schemas";7import {
8 OptionalTrimmedNonEmptyStringSchema,
9} from "../string-schemas";
8 10
9const LocalWorkspacePolicyPayloadSchema = Schema.Struct({11const LocalScopePolicyPayloadSchema = Schema.Struct({
10 resourcePattern: OptionalTrimmedNonEmptyStringSchema,12 resourcePattern: OptionalTrimmedNonEmptyStringSchema,
11 effect: Schema.optional(LocalWorkspacePolicyEffectSchema),13 effect: Schema.optional(LocalScopePolicyEffectSchema),
12 approvalMode: Schema.optional(LocalWorkspacePolicyApprovalModeSchema),14 approvalMode: Schema.optional(LocalScopePolicyApprovalModeSchema),
13 priority: Schema.optional(Schema.Number),15 priority: Schema.optional(Schema.Number),
14 enabled: Schema.optional(Schema.Boolean),16 enabled: Schema.optional(Schema.Boolean),
15});17});
16 18
17export const CreatePolicyPayloadSchema = LocalWorkspacePolicyPayloadSchema;19export const CreatePolicyPayloadSchema = LocalScopePolicyPayloadSchema;
18 20
19export type CreatePolicyPayload = typeof CreatePolicyPayloadSchema.Type;21export type CreatePolicyPayload = typeof CreatePolicyPayloadSchema.Type;
20 22
21export const UpdatePolicyPayloadSchema = LocalWorkspacePolicyPayloadSchema;23export const UpdatePolicyPayloadSchema = LocalScopePolicyPayloadSchema;
22 24
23export type UpdatePolicyPayload = typeof UpdatePolicyPayloadSchema.Type;25export type UpdatePolicyPayload = typeof UpdatePolicyPayloadSchema.Type;
packages/platform/sdk/src/policies/operations.ts+135−127
@@ -1,4 +1,6 @@
1import { createHash } from "node:crypto";1import {
2 createHash,
3} from "node:crypto";
2 4
3import type {5import type {
4 CreatePolicyPayload,6 CreatePolicyPayload,
@@ -7,23 +9,29 @@ import type { · 2 unmodified lines
7import {9import {
8 PolicyIdSchema,10 PolicyIdSchema,
9 type LocalExecutorConfig,11 type LocalExecutorConfig,
10 type LocalWorkspacePolicy,12 type LocalScopePolicy,
11 type PolicyId,13 type PolicyId,
12 type WorkspaceId,14 type ScopeId,
13} from "../schema";15} from "../schema";
14import * as Effect from "effect/Effect";16import * as Effect from "effect/Effect";
15 17
16import { requireRuntimeLocalWorkspace } from "../runtime/workspace/runtime-context";18import {
19 requireRuntimeLocalScope,
20} from "../runtime/scope/runtime-context";
17import type {21import type {
18 WorkspaceConfigStoreShape,22 ScopeConfigStoreShape,
19 WorkspaceStateStoreShape,23 ScopeStateStoreShape,
20} from "../runtime/workspace/storage";24} from "../runtime/scope/storage";
25import {
26 ScopeConfigStore,
27 ScopeStateStore,
28} from "../runtime/scope/storage";
29import {
30 type LocalScopeState,
31} from "../runtime/scope-state";
21import {32import {
22 WorkspaceConfigStore,33 derivePolicyConfigKey,
23 WorkspaceStateStore,34} from "../runtime/scope/scope-sync";
24} from "../runtime/workspace/storage";
25import { type LocalWorkspaceState } from "../runtime/workspace-state";
26import { derivePolicyConfigKey } from "../runtime/workspace/workspace-sync";
27import {35import {
28 type OperationErrors,36 type OperationErrors,
29 operationErrors,37 operationErrors,
@@ -39,34 +47,34 @@ const policyOps = { · 9 unmodified lines
39 47
40const cloneJson = <T>(value: T): T => JSON.parse(JSON.stringify(value)) as T;48const cloneJson = <T>(value: T): T => JSON.parse(JSON.stringify(value)) as T;
41 49
42const workspacePolicyStableKey = (input: {50const scopePolicyStableKey = (input: {
43 workspaceId: WorkspaceId;51 scopeId: ScopeId;
44 workspaceRoot?: string | null;52 scopeRoot?: string | null;
45}): string => input.workspaceRoot?.trim() || input.workspaceId;53}): string => input.scopeRoot?.trim() || input.scopeId;
46 54
47const localPolicyIdForKey = (input: {55const localPolicyIdForKey = (input: {
48 workspaceStableKey: string;56 scopeStableKey: string;
49 key: string;57 key: string;
50}): PolicyId =>58}): PolicyId =>
51 PolicyIdSchema.make(59 PolicyIdSchema.make(
52 `pol_local_${createHash("sha256").update(`${input.workspaceStableKey}:${input.key}`).digest("hex").slice(0, 16)}`,60 `pol_local_${createHash("sha256").update(`${input.scopeStableKey}:${input.key}`).digest("hex").slice(0, 16)}`,
53 );61 );
54 62
55const toLocalWorkspacePolicy = (input: {63const toLocalScopePolicy = (input: {
56 workspaceId: WorkspaceId;64 scopeId: ScopeId;
57 workspaceStableKey: string;65 scopeStableKey: string;
58 key: string;66 key: string;
59 policyConfig: NonNullable<LocalExecutorConfig["policies"]>[string];67 policyConfig: NonNullable<LocalExecutorConfig["policies"]>[string];
60 state: LocalWorkspaceState["policies"][string] | undefined;68 state: LocalScopeState["policies"][string] | undefined;
61}): LocalWorkspacePolicy => ({69}): LocalScopePolicy => ({
62 id:70 id:
63 input.state?.id ??71 input.state?.id ??
64 localPolicyIdForKey({72 localPolicyIdForKey({
65 workspaceStableKey: input.workspaceStableKey,73 scopeStableKey: input.scopeStableKey,
66 key: input.key,74 key: input.key,
67 }),75 }),
68 key: input.key,76 key: input.key,
69 workspaceId: input.workspaceId,77 scopeId: input.scopeId,
70 resourcePattern: input.policyConfig.match.trim(),78 resourcePattern: input.policyConfig.match.trim(),
71 effect: input.policyConfig.action,79 effect: input.policyConfig.action,
72 approvalMode: input.policyConfig.approval === "manual" ? "required" : "auto",80 approvalMode: input.policyConfig.approval === "manual" ? "required" : "auto",
@@ -76,51 +84,51 @@ const toLocalWorkspacePolicy = (input: { · 3 unmodified lines
76 updatedAt: input.state?.updatedAt ?? Date.now(),84 updatedAt: input.state?.updatedAt ?? Date.now(),
77});85});
78 86
79export const loadRuntimeLocalWorkspacePolicies = (workspaceId: WorkspaceId) =>87export const loadRuntimeLocalScopePolicies = (scopeId: ScopeId) =>
80 Effect.gen(function* () {88 Effect.gen(function* () {
81 const runtimeLocalWorkspace =89 const runtimeLocalScope =
82 yield* requireRuntimeLocalWorkspace(workspaceId);90 yield* requireRuntimeLocalScope(scopeId);
83 const workspaceConfigStore = yield* WorkspaceConfigStore;91 const scopeConfigStore = yield* ScopeConfigStore;
84 const workspaceStateStore = yield* WorkspaceStateStore;92 const scopeStateStore = yield* ScopeStateStore;
85 const loadedConfig = yield* workspaceConfigStore.load();93 const loadedConfig = yield* scopeConfigStore.load();
86 const workspaceState = yield* workspaceStateStore.load();94 const scopeState = yield* scopeStateStore.load();
87 95
88 const policies = Object.entries(loadedConfig.config?.policies ?? {}).map(96 const policies = Object.entries(loadedConfig.config?.policies ?? {}).map(
89 ([key, policyConfig]) =>97 ([key, policyConfig]) =>
90 toLocalWorkspacePolicy({98 toLocalScopePolicy({
91 workspaceId,99 scopeId,
92 workspaceStableKey: workspacePolicyStableKey({100 scopeStableKey: scopePolicyStableKey({
93 workspaceId,101 scopeId,
94 workspaceRoot: runtimeLocalWorkspace.workspace.workspaceRoot,102 scopeRoot: runtimeLocalScope.scope.scopeRoot,
95 }),103 }),
96 key,104 key,
97 policyConfig,105 policyConfig,
98 state: workspaceState.policies[key],106 state: scopeState.policies[key],
99 }),107 }),
100 );108 );
101 109
102 return {110 return {
103 runtimeLocalWorkspace,111 runtimeLocalScope,
104 loadedConfig,112 loadedConfig,
105 workspaceState,113 scopeState,
106 policies,114 policies,
107 };115 };
108 });116 });
109 117
110const writeLocalPolicyFiles = (input: {118const writeLocalPolicyFiles = (input: {
111 operation: OperationErrors;119 operation: OperationErrors;
112 workspaceConfigStore: WorkspaceConfigStoreShape;120 scopeConfigStore: ScopeConfigStoreShape;
113 workspaceStateStore: WorkspaceStateStoreShape;121 scopeStateStore: ScopeStateStoreShape;
114 projectConfig: LocalExecutorConfig;122 projectConfig: LocalExecutorConfig;
115 workspaceState: LocalWorkspaceState;123 scopeState: LocalScopeState;
116}) =>124}) =>
117 Effect.all(125 Effect.all(
118 [126 [
119 input.workspaceConfigStore.writeProject({127 input.scopeConfigStore.writeProject({
120 config: input.projectConfig,128 config: input.projectConfig,
121 }),129 }),
122 input.workspaceStateStore.write({130 input.scopeStateStore.write({
123 state: input.workspaceState,131 state: input.scopeState,
124 }),132 }),
125 ],133 ],
126 { discard: true },134 { discard: true },
@@ -128,16 +136,16 @@ const writeLocalPolicyFiles = (input: { · 1 unmodified line
128 Effect.mapError((cause) =>136 Effect.mapError((cause) =>
129 input.operation.unknownStorage(137 input.operation.unknownStorage(
130 cause,138 cause,
131 "Failed writing local workspace policy files",139 "Failed writing local scope policy files",
132 ),140 ),
133 ),141 ),
134 );142 );
135 143
136const loadWorkspacePolicyContext = (144const loadScopePolicyContext = (
137 operation: OperationErrors,145 operation: OperationErrors,
138 workspaceId: WorkspaceId,146 scopeId: ScopeId,
139) =>147) =>
140 requireRuntimeLocalWorkspace(workspaceId).pipe(148 requireRuntimeLocalScope(scopeId).pipe(
141 Effect.mapError((cause) =>149 Effect.mapError((cause) =>
142 operation.notFound(150 operation.notFound(
143 "Workspace not found",151 "Workspace not found",
@@ -146,46 +154,46 @@ const loadWorkspacePolicyContext = ( · 2 unmodified lines
146 ),154 ),
147 );155 );
148 156
149export const listPolicies = (workspaceId: WorkspaceId) =>157export const listPolicies = (scopeId: ScopeId) =>
150 Effect.gen(function* () {158 Effect.gen(function* () {
151 yield* loadWorkspacePolicyContext(policyOps.list, workspaceId);159 yield* loadScopePolicyContext(policyOps.list, scopeId);
152 const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(160 const localScope = yield* loadRuntimeLocalScopePolicies(
153 workspaceId,161 scopeId,
154 ).pipe(162 ).pipe(
155 Effect.mapError((cause) =>163 Effect.mapError((cause) =>
156 policyOps.list.unknownStorage(164 policyOps.list.unknownStorage(
157 cause,165 cause,
158 "Failed loading local workspace policies",166 "Failed loading local scope policies",
159 ),167 ),
160 ),168 ),
161 );169 );
162 return localWorkspace.policies;170 return localScope.policies;
163 });171 });
164 172
165export const createPolicy = (input: {173export const createPolicy = (input: {
166 workspaceId: WorkspaceId;174 scopeId: ScopeId;
167 payload: CreatePolicyPayload;175 payload: CreatePolicyPayload;
168}) =>176}) =>
169 Effect.gen(function* () {177 Effect.gen(function* () {
170 const runtimeLocalWorkspace = yield* loadWorkspacePolicyContext(178 const runtimeLocalScope = yield* loadScopePolicyContext(
171 policyOps.create,179 policyOps.create,
172 input.workspaceId,180 input.scopeId,
173 );181 );
174 const workspaceConfigStore = yield* WorkspaceConfigStore;182 const scopeConfigStore = yield* ScopeConfigStore;
175 const workspaceStateStore = yield* WorkspaceStateStore;183 const scopeStateStore = yield* ScopeStateStore;
176 const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(184 const localScope = yield* loadRuntimeLocalScopePolicies(
177 input.workspaceId,185 input.scopeId,
178 ).pipe(186 ).pipe(
179 Effect.mapError((cause) =>187 Effect.mapError((cause) =>
180 policyOps.create.unknownStorage(188 policyOps.create.unknownStorage(
181 cause,189 cause,
182 "Failed loading local workspace policies",190 "Failed loading local scope policies",
183 ),191 ),
184 ),192 ),
185 );193 );
186 const now = Date.now();194 const now = Date.now();
187 const projectConfig = cloneJson(195 const projectConfig = cloneJson(
188 localWorkspace.loadedConfig.projectConfig ?? {},196 localScope.loadedConfig.projectConfig ?? {},
189 );197 );
190 const policies = { ...projectConfig.policies };198 const policies = { ...projectConfig.policies };
191 const key = derivePolicyConfigKey(199 const key = derivePolicyConfigKey(
@@ -210,18 +218,18 @@ export const createPolicy = (input: { · 18 unmodified lines
210 : {}),218 : {}),
211 };219 };
212 220
213 const existingState = localWorkspace.workspaceState.policies[key];221 const existingState = localScope.scopeState.policies[key];
214 const workspaceState: LocalWorkspaceState = {222 const scopeState: LocalScopeState = {
215 ...localWorkspace.workspaceState,223 ...localScope.scopeState,
216 policies: {224 policies: {
217 ...localWorkspace.workspaceState.policies,225 ...localScope.scopeState.policies,
218 [key]: {226 [key]: {
219 id:227 id:
220 existingState?.id ??228 existingState?.id ??
221 localPolicyIdForKey({229 localPolicyIdForKey({
222 workspaceStableKey: workspacePolicyStableKey({230 scopeStableKey: scopePolicyStableKey({
223 workspaceId: input.workspaceId,231 scopeId: input.scopeId,
224 workspaceRoot: runtimeLocalWorkspace.workspace.workspaceRoot,232 scopeRoot: runtimeLocalScope.scope.scopeRoot,
225 }),233 }),
226 key,234 key,
227 }),235 }),
@@ -233,91 +241,91 @@ export const createPolicy = (input: { · 5 unmodified lines
233 241
234 yield* writeLocalPolicyFiles({242 yield* writeLocalPolicyFiles({
235 operation: policyOps.create,243 operation: policyOps.create,
236 workspaceConfigStore,244 scopeConfigStore,
237 workspaceStateStore,245 scopeStateStore,
238 projectConfig: {246 projectConfig: {
239 ...projectConfig,247 ...projectConfig,
240 policies,248 policies,
241 },249 },
242 workspaceState,250 scopeState,
243 });251 });
244 252
245 return toLocalWorkspacePolicy({253 return toLocalScopePolicy({
246 workspaceId: input.workspaceId,254 scopeId: input.scopeId,
247 workspaceStableKey: workspacePolicyStableKey({255 scopeStableKey: scopePolicyStableKey({
248 workspaceId: input.workspaceId,256 scopeId: input.scopeId,
249 workspaceRoot: runtimeLocalWorkspace.workspace.workspaceRoot,257 scopeRoot: runtimeLocalScope.scope.scopeRoot,
250 }),258 }),
251 key,259 key,
252 policyConfig: policies[key]!,260 policyConfig: policies[key]!,
253 state: workspaceState.policies[key],261 state: scopeState.policies[key],
254 });262 });
255 });263 });
256 264
257export const getPolicy = (input: {265export const getPolicy = (input: {
258 workspaceId: WorkspaceId;266 scopeId: ScopeId;
259 policyId: PolicyId;267 policyId: PolicyId;
260}) =>268}) =>
261 Effect.gen(function* () {269 Effect.gen(function* () {
262 yield* loadWorkspacePolicyContext(policyOps.get, input.workspaceId);270 yield* loadScopePolicyContext(policyOps.get, input.scopeId);
263 const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(271 const localScope = yield* loadRuntimeLocalScopePolicies(
264 input.workspaceId,272 input.scopeId,
265 ).pipe(273 ).pipe(
266 Effect.mapError((cause) =>274 Effect.mapError((cause) =>
267 policyOps.get.unknownStorage(275 policyOps.get.unknownStorage(
268 cause,276 cause,
269 "Failed loading local workspace policies",277 "Failed loading local scope policies",
270 ),278 ),
271 ),279 ),
272 );280 );
273 const policy =281 const policy =
274 localWorkspace.policies.find(282 localScope.policies.find(
275 (candidate) => candidate.id === input.policyId,283 (candidate) => candidate.id === input.policyId,
276 ) ?? null;284 ) ?? null;
277 if (policy === null) {285 if (policy === null) {
278 return yield* policyOps.get.notFound(286 return yield* policyOps.get.notFound(
279 "Policy not found",287 "Policy not found",
280 `workspaceId=${input.workspaceId} policyId=${input.policyId}`,288 `scopeId=${input.scopeId} policyId=${input.policyId}`,
281 );289 );
282 }290 }
283 return policy;291 return policy;
284 });292 });
285 293
286export const updatePolicy = (input: {294export const updatePolicy = (input: {
287 workspaceId: WorkspaceId;295 scopeId: ScopeId;
288 policyId: PolicyId;296 policyId: PolicyId;
289 payload: UpdatePolicyPayload;297 payload: UpdatePolicyPayload;
290}) =>298}) =>
291 Effect.gen(function* () {299 Effect.gen(function* () {
292 const runtimeLocalWorkspace = yield* loadWorkspacePolicyContext(300 const runtimeLocalScope = yield* loadScopePolicyContext(
293 policyOps.update,301 policyOps.update,
294 input.workspaceId,302 input.scopeId,
295 );303 );
296 const workspaceConfigStore = yield* WorkspaceConfigStore;304 const scopeConfigStore = yield* ScopeConfigStore;
297 const workspaceStateStore = yield* WorkspaceStateStore;305 const scopeStateStore = yield* ScopeStateStore;
298 const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(306 const localScope = yield* loadRuntimeLocalScopePolicies(
299 input.workspaceId,307 input.scopeId,
300 ).pipe(308 ).pipe(
301 Effect.mapError((cause) =>309 Effect.mapError((cause) =>
302 policyOps.update.unknownStorage(310 policyOps.update.unknownStorage(
303 cause,311 cause,
304 "Failed loading local workspace policies",312 "Failed loading local scope policies",
305 ),313 ),
306 ),314 ),
307 );315 );
308 const existing =316 const existing =
309 localWorkspace.policies.find(317 localScope.policies.find(
310 (candidate) => candidate.id === input.policyId,318 (candidate) => candidate.id === input.policyId,
311 ) ?? null;319 ) ?? null;
312 if (existing === null) {320 if (existing === null) {
313 return yield* policyOps.update.notFound(321 return yield* policyOps.update.notFound(
314 "Policy not found",322 "Policy not found",
315 `workspaceId=${input.workspaceId} policyId=${input.policyId}`,323 `scopeId=${input.scopeId} policyId=${input.policyId}`,
316 );324 );
317 }325 }
318 326
319 const projectConfig = cloneJson(327 const projectConfig = cloneJson(
320 localWorkspace.loadedConfig.projectConfig ?? {},328 localScope.loadedConfig.projectConfig ?? {},
321 );329 );
322 const policies = { ...projectConfig.policies };330 const policies = { ...projectConfig.policies };
323 const existingConfig = policies[existing.key]!;331 const existingConfig = policies[existing.key]!;
@@ -345,11 +353,11 @@ export const updatePolicy = (input: { · 21 unmodified lines
345 };353 };
346 354
347 const updatedAt = Date.now();355 const updatedAt = Date.now();
348 const existingState = localWorkspace.workspaceState.policies[existing.key];356 const existingState = localScope.scopeState.policies[existing.key];
349 const workspaceState: LocalWorkspaceState = {357 const scopeState: LocalScopeState = {
350 ...localWorkspace.workspaceState,358 ...localScope.scopeState,
351 policies: {359 policies: {
352 ...localWorkspace.workspaceState.policies,360 ...localScope.scopeState.policies,
353 [existing.key]: {361 [existing.key]: {
354 id: existing.id,362 id: existing.id,
355 createdAt: existingState?.createdAt ?? existing.createdAt,363 createdAt: existingState?.createdAt ?? existing.createdAt,
@@ -360,50 +368,50 @@ export const updatePolicy = (input: { · 4 unmodified lines
360 368
361 yield* writeLocalPolicyFiles({369 yield* writeLocalPolicyFiles({
362 operation: policyOps.update,370 operation: policyOps.update,
363 workspaceConfigStore,371 scopeConfigStore,
364 workspaceStateStore,372 scopeStateStore,
365 projectConfig: {373 projectConfig: {
366 ...projectConfig,374 ...projectConfig,
367 policies,375 policies,
368 },376 },
369 workspaceState,377 scopeState,
370 });378 });
371 379
372 return toLocalWorkspacePolicy({380 return toLocalScopePolicy({
373 workspaceId: input.workspaceId,381 scopeId: input.scopeId,
374 workspaceStableKey: workspacePolicyStableKey({382 scopeStableKey: scopePolicyStableKey({
375 workspaceId: input.workspaceId,383 scopeId: input.scopeId,
376 workspaceRoot: runtimeLocalWorkspace.workspace.workspaceRoot,384 scopeRoot: runtimeLocalScope.scope.scopeRoot,
377 }),385 }),
378 key: existing.key,386 key: existing.key,
379 policyConfig: policies[existing.key]!,387 policyConfig: policies[existing.key]!,
380 state: workspaceState.policies[existing.key],388 state: scopeState.policies[existing.key],
381 });389 });
382 });390 });
383 391
384export const removePolicy = (input: {392export const removePolicy = (input: {
385 workspaceId: WorkspaceId;393 scopeId: ScopeId;
386 policyId: PolicyId;394 policyId: PolicyId;
387}) =>395}) =>
388 Effect.gen(function* () {396 Effect.gen(function* () {
389 const runtimeLocalWorkspace = yield* loadWorkspacePolicyContext(397 const runtimeLocalScope = yield* loadScopePolicyContext(
390 policyOps.remove,398 policyOps.remove,
391 input.workspaceId,399 input.scopeId,
392 );400 );
393 const workspaceConfigStore = yield* WorkspaceConfigStore;401 const scopeConfigStore = yield* ScopeConfigStore;
394 const workspaceStateStore = yield* WorkspaceStateStore;402 const scopeStateStore = yield* ScopeStateStore;
395 const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(403 const localScope = yield* loadRuntimeLocalScopePolicies(
396 input.workspaceId,404 input.scopeId,
397 ).pipe(405 ).pipe(
398 Effect.mapError((cause) =>406 Effect.mapError((cause) =>
399 policyOps.remove.unknownStorage(407 policyOps.remove.unknownStorage(
400 cause,408 cause,
401 "Failed loading local workspace policies",409 "Failed loading local scope policies",
402 ),410 ),
403 ),411 ),
404 );412 );
405 const existing =413 const existing =
406 localWorkspace.policies.find(414 localScope.policies.find(
407 (candidate) => candidate.id === input.policyId,415 (candidate) => candidate.id === input.policyId,
408 ) ?? null;416 ) ?? null;
409 if (existing === null) {417 if (existing === null) {
@@ -411,23 +419,23 @@ export const removePolicy = (input: { · 1 unmodified line
411 }419 }
412 420
413 const projectConfig = cloneJson(421 const projectConfig = cloneJson(
414 localWorkspace.loadedConfig.projectConfig ?? {},422 localScope.loadedConfig.projectConfig ?? {},
415 );423 );
416 const policies = { ...projectConfig.policies };424 const policies = { ...projectConfig.policies };
417 delete policies[existing.key];425 delete policies[existing.key];
418 426
419 const { [existing.key]: _removedPolicy, ...remainingPolicies } =427 const { [existing.key]: _removedPolicy, ...remainingPolicies } =
420 localWorkspace.workspaceState.policies;428 localScope.scopeState.policies;
421 yield* writeLocalPolicyFiles({429 yield* writeLocalPolicyFiles({
422 operation: policyOps.remove,430 operation: policyOps.remove,
423 workspaceConfigStore,431 scopeConfigStore,
424 workspaceStateStore,432 scopeStateStore,
425 projectConfig: {433 projectConfig: {
426 ...projectConfig,434 ...projectConfig,
427 policies,435 policies,
428 },436 },
429 workspaceState: {437 scopeState: {
430 ...localWorkspace.workspaceState,438 ...localScope.scopeState,
431 policies: remainingPolicies,439 policies: remainingPolicies,
432 },440 },
433 });441 });
packages/platform/sdk/src/runtime/auth/auth-artifacts.ts+19−15
@@ -1,6 +1,8 @@
1import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";1import type {
2 OAuthClientProvider,
3} from "@modelcontextprotocol/sdk/client/auth.js";
2import {4import {
3 type AccountId,5 type ScopeId,
4 AuthGrantSetJsonSchema,6 AuthGrantSetJsonSchema,
5 McpOAuthAuthArtifactConfigJsonSchema,7 McpOAuthAuthArtifactConfigJsonSchema,
6 McpOAuthAuthArtifactKind,8 McpOAuthAuthArtifactKind,
@@ -35,8 +37,10 @@ import * as Schema from "effect/Schema"; · 28 unmodified lines
35import type {37import type {
36 ResolveSecretMaterial,38 ResolveSecretMaterial,
37 SecretMaterialResolveContext,39 SecretMaterialResolveContext,
38} from "../workspace/secret-material-providers";40} from "../scope/secret-material-providers";
39import { runtimeEffectError } from "../effect-errors";41import {
42 runtimeEffectError,
43} from "../effect-errors";
40 44
41export type ResolvedSourceAuthMaterial = {45export type ResolvedSourceAuthMaterial = {
42 placements: ReadonlyArray<RequestPlacement>;46 placements: ReadonlyArray<RequestPlacement>;
@@ -154,7 +158,7 @@ export const authArtifactFromSourceAuth = (input: { · 111 unmodified lines
154 source: Source;158 source: Source;
155 auth: SourceAuth;159 auth: SourceAuth;
156 slot: CredentialSlot;160 slot: CredentialSlot;
157 actorAccountId?: AccountId | null;161 actorScopeId?: ScopeId | null;
158 existingAuthArtifactId?: AuthArtifact["id"] | null;162 existingAuthArtifactId?: AuthArtifact["id"] | null;
159}): AuthArtifact | null => {163}): AuthArtifact | null => {
160 if (input.auth.kind === "none") {164 if (input.auth.kind === "none") {
@@ -170,9 +174,9 @@ export const authArtifactFromSourceAuth = (input: { · 9 unmodified lines
170 if (input.auth.kind === "bearer") {174 if (input.auth.kind === "bearer") {
171 return {175 return {
172 id,176 id,
173 workspaceId: input.source.workspaceId,177 scopeId: input.source.scopeId,
174 sourceId: input.source.id,178 sourceId: input.source.id,
175 actorAccountId: input.actorAccountId ?? null,179 actorScopeId: input.actorScopeId ?? null,
176 slot: input.slot,180 slot: input.slot,
177 artifactKind: StaticBearerAuthArtifactKind,181 artifactKind: StaticBearerAuthArtifactKind,
178 configJson: encodeStaticBearerAuthArtifactConfig({182 configJson: encodeStaticBearerAuthArtifactConfig({
@@ -189,9 +193,9 @@ export const authArtifactFromSourceAuth = (input: { · 10 unmodified lines
189 if (input.auth.kind === "oauth2_authorized_user") {193 if (input.auth.kind === "oauth2_authorized_user") {
190 return {194 return {
191 id,195 id,
192 workspaceId: input.source.workspaceId,196 scopeId: input.source.scopeId,
193 sourceId: input.source.id,197 sourceId: input.source.id,
194 actorAccountId: input.actorAccountId ?? null,198 actorScopeId: input.actorScopeId ?? null,
195 slot: input.slot,199 slot: input.slot,
196 artifactKind: RefreshableOAuth2AuthorizedUserAuthArtifactKind,200 artifactKind: RefreshableOAuth2AuthorizedUserAuthArtifactKind,
197 configJson: encodeRefreshableOAuth2AuthorizedUserAuthArtifactConfig({201 configJson: encodeRefreshableOAuth2AuthorizedUserAuthArtifactConfig({
@@ -212,9 +216,9 @@ export const authArtifactFromSourceAuth = (input: { · 14 unmodified lines
212 if (input.auth.kind === "provider_grant_ref") {216 if (input.auth.kind === "provider_grant_ref") {
213 return {217 return {
214 id,218 id,
215 workspaceId: input.source.workspaceId,219 scopeId: input.source.scopeId,
216 sourceId: input.source.id,220 sourceId: input.source.id,
217 actorAccountId: input.actorAccountId ?? null,221 actorScopeId: input.actorScopeId ?? null,
218 slot: input.slot,222 slot: input.slot,
219 artifactKind: ProviderGrantRefAuthArtifactKind,223 artifactKind: ProviderGrantRefAuthArtifactKind,
220 configJson: encodeProviderGrantRefAuthArtifactConfig({224 configJson: encodeProviderGrantRefAuthArtifactConfig({
@@ -233,9 +237,9 @@ export const authArtifactFromSourceAuth = (input: { · 12 unmodified lines
233 if (input.auth.kind === "mcp_oauth") {237 if (input.auth.kind === "mcp_oauth") {
234 return {238 return {
235 id,239 id,
236 workspaceId: input.source.workspaceId,240 scopeId: input.source.scopeId,
237 sourceId: input.source.id,241 sourceId: input.source.id,
238 actorAccountId: input.actorAccountId ?? null,242 actorScopeId: input.actorScopeId ?? null,
239 slot: input.slot,243 slot: input.slot,
240 artifactKind: McpOAuthAuthArtifactKind,244 artifactKind: McpOAuthAuthArtifactKind,
241 configJson: encodeMcpOAuthAuthArtifactConfig({245 configJson: encodeMcpOAuthAuthArtifactConfig({
@@ -259,9 +263,9 @@ export const authArtifactFromSourceAuth = (input: { · 17 unmodified lines
259 263
260 return {264 return {
261 id,265 id,
262 workspaceId: input.source.workspaceId,266 scopeId: input.source.scopeId,
263 sourceId: input.source.id,267 sourceId: input.source.id,
264 actorAccountId: input.actorAccountId ?? null,268 actorScopeId: input.actorScopeId ?? null,
265 slot: input.slot,269 slot: input.slot,
266 artifactKind: StaticOAuth2AuthArtifactKind,270 artifactKind: StaticOAuth2AuthArtifactKind,
267 configJson: encodeStaticOAuth2AuthArtifactConfig({271 configJson: encodeStaticOAuth2AuthArtifactConfig({
packages/platform/sdk/src/runtime/auth/auth-leases.ts+30−17
@@ -16,18 +16,31 @@ import { · 15 unmodified lines
16import * as Effect from "effect/Effect";16import * as Effect from "effect/Effect";
17import * as Option from "effect/Option";17import * as Option from "effect/Option";
18 18
19import type { ExecutorStateStoreShape } from "../executor-state-store";19import type {
20import { resolveAuthArtifactMaterial, type ResolvedSourceAuthMaterial } from "./auth-artifacts";20 ExecutorStateStoreShape,
21} from "../executor-state-store";
22import {
23 resolveAuthArtifactMaterial,
24 type ResolvedSourceAuthMaterial,
25} from "./auth-artifacts";
21import {26import {
22 type DeleteSecretMaterial,27 type DeleteSecretMaterial,
23 type ResolveSecretMaterial,28 type ResolveSecretMaterial,
24 type SecretMaterialResolveContext,29 type SecretMaterialResolveContext,
25 type StoreSecretMaterial,30 type StoreSecretMaterial,
26} from "../workspace/secret-material-providers";31} from "../scope/secret-material-providers";
27import { refreshOAuth2AccessToken } from "./oauth2-pkce";32import {
28import type { OAuth2TokenResponse } from "./oauth2-pkce";33 refreshOAuth2AccessToken,
29import { createPersistedMcpAuthProvider } from "./mcp-auth-provider";34} from "./oauth2-pkce";
30import { runtimeEffectError } from "../effect-errors";35import type {
36 OAuth2TokenResponse,
37} from "./oauth2-pkce";
38import {
39 createPersistedMcpAuthProvider,
40} from "./mcp-auth-provider";
41import {
42 runtimeEffectError,
43} from "../effect-errors";
31 44
32const LEASE_REFRESH_SKEW_MS = 60_000;45const LEASE_REFRESH_SKEW_MS = 60_000;
33 46
@@ -134,9 +147,9 @@ const refreshRefreshableOauth2AuthorizedUserArtifact = (input: { · 100 unmodified lines
134 const nextLease: AuthLease = {147 const nextLease: AuthLease = {
135 id: input.lease?.id ?? AuthLeaseIdSchema.make(`auth_lease_${crypto.randomUUID()}`),148 id: input.lease?.id ?? AuthLeaseIdSchema.make(`auth_lease_${crypto.randomUUID()}`),
136 authArtifactId: input.artifact.id,149 authArtifactId: input.artifact.id,
137 workspaceId: input.artifact.workspaceId,150 scopeId: input.artifact.scopeId,
138 sourceId: input.artifact.sourceId,151 sourceId: input.artifact.sourceId,
139 actorAccountId: input.artifact.actorAccountId,152 actorScopeId: input.artifact.actorScopeId,
140 slot: input.artifact.slot,153 slot: input.artifact.slot,
141 placementsTemplateJson: encodeLeasePlacementTemplatesJson([154 placementsTemplateJson: encodeLeasePlacementTemplatesJson([
142 {155 {
@@ -169,7 +182,7 @@ const refreshRefreshableOauth2AuthorizedUserArtifact = (input: { · 26 unmodified lines
169 return nextLease;182 return nextLease;
170 });183 });
171 184
172const workspaceOauthClientSecretRef = (input: {185const scopeOauthClientSecretRef = (input: {
173 clientSecretProviderId: string | null;186 clientSecretProviderId: string | null;
174 clientSecretHandle: string | null;187 clientSecretHandle: string | null;
175}): SecretRef | null =>188}): SecretRef | null =>
@@ -212,9 +225,9 @@ const refreshProviderGrantRefArtifact = (input: { · 36 unmodified lines
212 }225 }
213 const grant = grantOption.value;226 const grant = grantOption.value;
214 227
215 const oauthClientOption = yield* input.executorState.workspaceOauthClients.getById(grant.oauthClientId);228 const oauthClientOption = yield* input.executorState.scopeOauthClients.getById(grant.oauthClientId);
216 if (Option.isNone(oauthClientOption)) {229 if (Option.isNone(oauthClientOption)) {
217 return yield* runtimeEffectError("auth/auth-leases", `Workspace OAuth client not found: ${grant.oauthClientId}`);230 return yield* runtimeEffectError("auth/auth-leases", `Scope OAuth client not found: ${grant.oauthClientId}`);
218 }231 }
219 const oauthClient = oauthClientOption.value;232 const oauthClient = oauthClientOption.value;
220 233
@@ -222,7 +235,7 @@ const refreshProviderGrantRefArtifact = (input: { · 1 unmodified line
222 ref: grant.refreshToken,235 ref: grant.refreshToken,
223 context: input.context,236 context: input.context,
224 });237 });
225 const clientSecretRef = workspaceOauthClientSecretRef(oauthClient);238 const clientSecretRef = scopeOauthClientSecretRef(oauthClient);
226 const clientSecret = clientSecretRef239 const clientSecret = clientSecretRef
227 ? yield* input.resolveSecretMaterial({240 ? yield* input.resolveSecretMaterial({
228 ref: clientSecretRef,241 ref: clientSecretRef,
@@ -277,9 +290,9 @@ const refreshProviderGrantRefArtifact = (input: { · 48 unmodified lines
277 const nextLease: AuthLease = {290 const nextLease: AuthLease = {
278 id: input.lease?.id ?? AuthLeaseIdSchema.make(`auth_lease_${crypto.randomUUID()}`),291 id: input.lease?.id ?? AuthLeaseIdSchema.make(`auth_lease_${crypto.randomUUID()}`),
279 authArtifactId: input.artifact.id,292 authArtifactId: input.artifact.id,
280 workspaceId: input.artifact.workspaceId,293 scopeId: input.artifact.scopeId,
281 sourceId: input.artifact.sourceId,294 sourceId: input.artifact.sourceId,
282 actorAccountId: input.artifact.actorAccountId,295 actorScopeId: input.artifact.actorScopeId,
283 slot: input.artifact.slot,296 slot: input.artifact.slot,
284 placementsTemplateJson: encodeLeasePlacementTemplatesJson([297 placementsTemplateJson: encodeLeasePlacementTemplatesJson([
285 {298 {
@@ -363,9 +376,9 @@ export const upsertOauth2AuthorizedUserLeaseFromTokenResponse = (input: { · 77 unmodified lines
363 const nextLease: AuthLease = {376 const nextLease: AuthLease = {
364 id: existingLease?.id ?? AuthLeaseIdSchema.make(`auth_lease_${crypto.randomUUID()}`),377 id: existingLease?.id ?? AuthLeaseIdSchema.make(`auth_lease_${crypto.randomUUID()}`),
365 authArtifactId: input.artifact.id,378 authArtifactId: input.artifact.id,
366 workspaceId: input.artifact.workspaceId,379 scopeId: input.artifact.scopeId,
367 sourceId: input.artifact.sourceId,380 sourceId: input.artifact.sourceId,
368 actorAccountId: input.artifact.actorAccountId,381 actorScopeId: input.artifact.actorScopeId,
369 slot: input.artifact.slot,382 slot: input.artifact.slot,
370 placementsTemplateJson: encodeLeasePlacementTemplatesJson([383 placementsTemplateJson: encodeLeasePlacementTemplatesJson([
371 {384 {
packages/platform/sdk/src/runtime/auth/mcp-auth-provider.ts+4−2
@@ -20,13 +20,15 @@ import { · 19 unmodified lines
20import * as Effect from "effect/Effect";20import * as Effect from "effect/Effect";
21import * as Schema from "effect/Schema";21import * as Schema from "effect/Schema";
22 22
23import type { ExecutorStateStoreShape } from "../executor-state-store";23import type {
24 ExecutorStateStoreShape,
25} from "../executor-state-store";
24import {26import {
25 type DeleteSecretMaterial,27 type DeleteSecretMaterial,
26 type ResolveSecretMaterial,28 type ResolveSecretMaterial,
27 type SecretMaterialResolveContext,29 type SecretMaterialResolveContext,
28 type StoreSecretMaterial,30 type StoreSecretMaterial,
29} from "../workspace/secret-material-providers";31} from "../scope/secret-material-providers";
30 32
31const encodeMcpOAuthAuthArtifactConfig = Schema.encodeSync(33const encodeMcpOAuthAuthArtifactConfig = Schema.encodeSync(
32 McpOAuthAuthArtifactConfigJsonSchema,34 McpOAuthAuthArtifactConfigJsonSchema,
packages/platform/sdk/src/runtime/auth/oauth-loopback.test.ts+11−3
@@ -1,9 +1,17 @@
1import { createServer } from "node:http";1import {
2 createServer,
3} from "node:http";
2 4
3import { describe, expect, it } from "@effect/vitest";5import {
6 describe,
7 expect,
8 it,
9} from "@effect/vitest";
4import * as Effect from "effect/Effect";10import * as Effect from "effect/Effect";
5 11
6import { startOauthLoopbackRedirectServer } from "./oauth-loopback";12import {
13 startOauthLoopbackRedirectServer,
14} from "./oauth-loopback";
7 15
8const toError = (cause: unknown): Error =>16const toError = (cause: unknown): Error =>
9 cause instanceof Error ? cause : new Error(String(cause));17 cause instanceof Error ? cause : new Error(String(cause));
packages/platform/sdk/src/runtime/auth/oauth-loopback.ts+3−1
@@ -1,4 +1,6 @@
1import { createServer } from "node:http";1import {
2 createServer,
3} from "node:http";
2 4
3import * as Effect from "effect/Effect";5import * as Effect from "effect/Effect";
4 6
packages/platform/sdk/src/runtime/auth/provider-grant-lifecycle.ts+9−7
@@ -1,7 +1,7 @@
1import type {1import type {
2 AuthArtifact,2 AuthArtifact,
3 ProviderAuthGrant,3 ProviderAuthGrant,
4 WorkspaceId,4 ScopeId,
5} from "#schema";5} from "#schema";
6import {6import {
7 decodeProviderGrantRefAuthArtifactConfig,7 decodeProviderGrantRefAuthArtifactConfig,
@@ -11,8 +11,10 @@ import * as Option from "effect/Option"; · 3 unmodified lines
11 11
12import {12import {
13 type DeleteSecretMaterial,13 type DeleteSecretMaterial,
14} from "../workspace/secret-material-providers";14} from "../scope/secret-material-providers";
15import type { ExecutorStateStoreShape } from "../executor-state-store";15import type {
16 ExecutorStateStoreShape,
17} from "../executor-state-store";
16 18
17const providerGrantRefFromArtifact = (19const providerGrantRefFromArtifact = (
18 artifact: Pick<AuthArtifact, "artifactKind" | "configJson">,20 artifact: Pick<AuthArtifact, "artifactKind" | "configJson">,
@@ -24,11 +26,11 @@ export const providerGrantIdFromArtifact = ( · 5 unmodified lines
24 providerGrantRefFromArtifact(artifact)?.grantId ?? null;26 providerGrantRefFromArtifact(artifact)?.grantId ?? null;
25 27
26export const listProviderGrantRefArtifacts = (executorState: ExecutorStateStoreShape, input: {28export const listProviderGrantRefArtifacts = (executorState: ExecutorStateStoreShape, input: {
27 workspaceId: WorkspaceId;29 scopeId: ScopeId;
28 grantId?: ProviderAuthGrant["id"] | null;30 grantId?: ProviderAuthGrant["id"] | null;
29}): Effect.Effect<readonly AuthArtifact[], Error, never> =>31}): Effect.Effect<readonly AuthArtifact[], Error, never> =>
30 Effect.map(32 Effect.map(
31 executorState.authArtifacts.listByWorkspaceId(input.workspaceId),33 executorState.authArtifacts.listByScopeId(input.scopeId),
32 (artifacts) =>34 (artifacts) =>
33 artifacts.filter((artifact) => {35 artifacts.filter((artifact) => {
34 const grantId = providerGrantIdFromArtifact(artifact);36 const grantId = providerGrantIdFromArtifact(artifact);
@@ -54,7 +56,7 @@ export const clearProviderGrantOrphanedAt = (executorState: ExecutorStateStoreSh · 19 unmodified lines
54 });56 });
55 57
56export const markProviderGrantOrphanedIfUnused = (executorState: ExecutorStateStoreShape, input: {58export const markProviderGrantOrphanedIfUnused = (executorState: ExecutorStateStoreShape, input: {
57 workspaceId: WorkspaceId;59 scopeId: ScopeId;
58 grantId: ProviderAuthGrant["id"];60 grantId: ProviderAuthGrant["id"];
59}): Effect.Effect<boolean, Error, never> =>61}): Effect.Effect<boolean, Error, never> =>
60 Effect.gen(function* () {62 Effect.gen(function* () {
@@ -64,7 +66,7 @@ export const markProviderGrantOrphanedIfUnused = (executorState: ExecutorStateSt · 3 unmodified lines
64 }66 }
65 67
66 const grantOption = yield* executorState.providerAuthGrants.getById(input.grantId);68 const grantOption = yield* executorState.providerAuthGrants.getById(input.grantId);
67 if (Option.isNone(grantOption) || grantOption.value.workspaceId !== input.workspaceId) {69 if (Option.isNone(grantOption) || grantOption.value.scopeId !== input.scopeId) {
68 return false;70 return false;
69 }71 }
70 72
packages/platform/sdk/src/runtime/auth/source-auth-material.test.ts+14−8
@@ -1,19 +1,25 @@
1import { describe, expect, it } from "@effect/vitest";1import {
2 describe,
3 expect,
4 it,
5} from "@effect/vitest";
2import * as Effect from "effect/Effect";6import * as Effect from "effect/Effect";
3import * as Option from "effect/Option";7import * as Option from "effect/Option";
4 8
5import {9import {
6 AuthArtifactIdSchema,10 AuthArtifactIdSchema,
7 SourceIdSchema,11 SourceIdSchema,
8 WorkspaceIdSchema,12 ScopeIdSchema,
9 type Source,13 type Source,
10} from "#schema";14} from "#schema";
11 15
12import { resolveSourceAuthMaterialWithDeps } from "./source-auth-material";16import {
17 resolveSourceAuthMaterialWithDeps,
18} from "./source-auth-material";
13 19
14const makeSource = (overrides: Partial<Source> = {}): Source => ({20const makeSource = (overrides: Partial<Source> = {}): Source => ({
15 id: SourceIdSchema.make("src_tool_artifacts"),21 id: SourceIdSchema.make("src_tool_artifacts"),
16 workspaceId: WorkspaceIdSchema.make("ws_tool_artifacts"),22 scopeId: ScopeIdSchema.make("ws_tool_artifacts"),
17 name: "GitHub",23 name: "GitHub",
18 kind: "openapi",24 kind: "openapi",
19 endpoint: "https://api.github.com",25 endpoint: "https://api.github.com",
@@ -176,16 +182,16 @@ describe("source-auth-material", () => { · 156 unmodified lines
176 },182 },
177 auth: { kind: "none" },183 auth: { kind: "none" },
178 }),184 }),
179 actorAccountId: null,185 actorScopeId: null,
180 executorState: {186 executorState: {
181 authArtifacts: {187 authArtifacts: {
182 getByWorkspaceSourceAndActor: () =>188 getByScopeSourceAndActor: () =>
183 Effect.succeed(189 Effect.succeed(
184 Option.some({190 Option.some({
185 id: AuthArtifactIdSchema.make("auth_artifact_mcp"),191 id: AuthArtifactIdSchema.make("auth_artifact_mcp"),
186 workspaceId: WorkspaceIdSchema.make("ws_tool_artifacts"),192 scopeId: ScopeIdSchema.make("ws_tool_artifacts"),
187 sourceId: SourceIdSchema.make("src_tool_artifacts"),193 sourceId: SourceIdSchema.make("src_tool_artifacts"),
188 actorAccountId: null,194 actorScopeId: null,
189 slot: "runtime",195 slot: "runtime",
190 artifactKind: "mcp_oauth",196 artifactKind: "mcp_oauth",
191 configJson: JSON.stringify({197 configJson: JSON.stringify({
packages/platform/sdk/src/runtime/auth/source-auth-material.ts+20−13
@@ -1,5 +1,5 @@
1import type {1import type {
2 AccountId,2 ScopeId,
3 CredentialSlot,3 CredentialSlot,
4 Source,4 Source,
5 SourceAuth,5 SourceAuth,
@@ -9,25 +9,32 @@ import * as Effect from "effect/Effect"; · 3 unmodified lines
9import * as Layer from "effect/Layer";9import * as Layer from "effect/Layer";
10import * as Option from "effect/Option";10import * as Option from "effect/Option";
11 11
12import { ExecutorStateStore, type ExecutorStateStoreShape } from "../executor-state-store";12import {
13 ExecutorStateStore,
14 type ExecutorStateStoreShape,
15} from "../executor-state-store";
13import {16import {
14 authArtifactFromSourceAuth,17 authArtifactFromSourceAuth,
15 resolveAuthArtifactMaterial,18 resolveAuthArtifactMaterial,
16 type ResolvedSourceAuthMaterial,19 type ResolvedSourceAuthMaterial,
17} from "./auth-artifacts";20} from "./auth-artifacts";
18import { resolveAuthArtifactMaterialWithLeases } from "./auth-leases";21import {
22 resolveAuthArtifactMaterialWithLeases,
23} from "./auth-leases";
19import type {24import type {
20 DeleteSecretMaterial,25 DeleteSecretMaterial,
21 ResolveSecretMaterial,26 ResolveSecretMaterial,
22 SecretMaterialResolveContext,27 SecretMaterialResolveContext,
23 StoreSecretMaterial,28 StoreSecretMaterial,
24} from "../workspace/secret-material-providers";29} from "../scope/secret-material-providers";
25import {30import {
26 SecretMaterialDeleterService,31 SecretMaterialDeleterService,
27 SecretMaterialResolverService,32 SecretMaterialResolverService,
28 SecretMaterialStorerService,33 SecretMaterialStorerService,
29} from "../workspace/secret-material-providers";34} from "../scope/secret-material-providers";
30import { runtimeEffectError } from "../effect-errors";35import {
36 runtimeEffectError,
37} from "../effect-errors";
31 38
32const authForSlot = (input: {39const authForSlot = (input: {
33 source: Source;40 source: Source;
@@ -52,7 +59,7 @@ export type RuntimeSourceAuthMaterialShape = { · 18 unmodified lines
52 resolve: (input: {59 resolve: (input: {
53 source: Source;60 source: Source;
54 slot?: CredentialSlot;61 slot?: CredentialSlot;
55 actorAccountId?: AccountId | null;62 actorScopeId?: ScopeId | null;
56 context?: SecretMaterialResolveContext;63 context?: SecretMaterialResolveContext;
57 }) => Effect.Effect<ResolvedSourceAuthMaterial, Error, never>;64 }) => Effect.Effect<ResolvedSourceAuthMaterial, Error, never>;
58};65};
@@ -64,7 +71,7 @@ export class RuntimeSourceAuthMaterialService extends Context.Tag( · 5 unmodified lines
64export const resolveSourceAuthMaterialWithDeps = (input: {71export const resolveSourceAuthMaterialWithDeps = (input: {
65 source: Source;72 source: Source;
66 slot?: CredentialSlot;73 slot?: CredentialSlot;
67 actorAccountId?: AccountId | null;74 actorScopeId?: ScopeId | null;
68 executorState?: ExecutorStateStoreShape;75 executorState?: ExecutorStateStoreShape;
69 resolveSecretMaterial: ResolveSecretMaterial;76 resolveSecretMaterial: ResolveSecretMaterial;
70 storeSecretMaterial?: StoreSecretMaterial;77 storeSecretMaterial?: StoreSecretMaterial;
@@ -81,10 +88,10 @@ export const resolveSourceAuthMaterialWithDeps = (input: { · 10 unmodified lines
81 : [slot] satisfies ReadonlyArray<CredentialSlot>;88 : [slot] satisfies ReadonlyArray<CredentialSlot>;
82 89
83 for (const candidateSlot of candidateSlots) {90 for (const candidateSlot of candidateSlots) {
84 const artifactOption = yield* input.executorState.authArtifacts.getByWorkspaceSourceAndActor({91 const artifactOption = yield* input.executorState.authArtifacts.getByScopeSourceAndActor({
85 workspaceId: input.source.workspaceId,92 scopeId: input.source.scopeId,
86 sourceId: input.source.id,93 sourceId: input.source.id,
87 actorAccountId: input.actorAccountId ?? null,94 actorScopeId: input.actorScopeId ?? null,
88 slot: candidateSlot,95 slot: candidateSlot,
89 });96 });
90 97
@@ -108,7 +115,7 @@ export const resolveSourceAuthMaterialWithDeps = (input: { · 17 unmodified lines
108 slot,115 slot,
109 }),116 }),
110 slot,117 slot,
111 actorAccountId: input.actorAccountId ?? null,118 actorScopeId: input.actorScopeId ?? null,
112 });119 });
113 120
114 if (input.executorState !== undefined) {121 if (input.executorState !== undefined) {
@@ -140,7 +147,7 @@ export const resolveSourceAuthMaterialWithDeps = (input: { · 25 unmodified lines
140export const resolveSourceAuthMaterial = (input: {147export const resolveSourceAuthMaterial = (input: {
141 source: Source;148 source: Source;
142 slot?: CredentialSlot;149 slot?: CredentialSlot;
143 actorAccountId?: AccountId | null;150 actorScopeId?: ScopeId | null;
144 context?: SecretMaterialResolveContext;151 context?: SecretMaterialResolveContext;
145}): Effect.Effect<ResolvedSourceAuthMaterial, Error, RuntimeSourceAuthMaterialService> =>152}): Effect.Effect<ResolvedSourceAuthMaterial, Error, RuntimeSourceAuthMaterialService> =>
146 Effect.flatMap(RuntimeSourceAuthMaterialService, (service) => service.resolve(input));153 Effect.flatMap(RuntimeSourceAuthMaterialService, (service) => service.resolve(input));
packages/platform/sdk/src/runtime/catalog/catalog-typescript.ts+9−3
@@ -1,7 +1,13 @@
1import { createHash } from "node:crypto";1import {
2 createHash,
3} from "node:crypto";
2 4
3import type { ProjectedCatalog } from "@executor/ir/catalog";5import type {
4import type { ShapeSymbolId } from "@executor/ir/ids";6 ProjectedCatalog,
7} from "@executor/ir/catalog";
8import type {
9 ShapeSymbolId,
10} from "@executor/ir/ids";
5import type {11import type {
6 CatalogV1,12 CatalogV1,
7 DocumentationBlock,13 DocumentationBlock,
packages/platform/sdk/src/runtime/catalog/prettier-format.ts+3−1
@@ -1,4 +1,6 @@
1import { format } from "prettier/standalone";1import {
2 format,
3} from "prettier/standalone";
2import parserBabel from "prettier/plugins/babel";4import parserBabel from "prettier/plugins/babel";
3import parserEstree from "prettier/plugins/estree";5import parserEstree from "prettier/plugins/estree";
4import parserTypescript from "prettier/plugins/typescript";6import parserTypescript from "prettier/plugins/typescript";
packages/platform/sdk/src/runtime/catalog/source/reconcile.ts+24−15
@@ -1,15 +1,24 @@
1import type {1import type {
2 AccountId,2 ScopeId,
3 Source,3 Source,
4 WorkspaceId,
5} from "#schema";4} from "#schema";
6import * as Effect from "effect/Effect";5import * as Effect from "effect/Effect";
7 6
8import { RuntimeLocalWorkspaceService } from "../../workspace/runtime-context";7import {
9import { SourceArtifactStore } from "../../workspace/storage";8 RuntimeLocalScopeService,
10import { getSourceAdapterForSource } from "../../sources/source-adapters";9} from "../../scope/runtime-context";
11import { RuntimeSourceStoreService } from "../../sources/source-store";10import {
12import { RuntimeSourceCatalogSyncService } from "./sync";11 SourceArtifactStore,
12} from "../../scope/storage";
13import {
14 getSourceAdapterForSource,
15} from "../../sources/source-adapters";
16import {
17 RuntimeSourceStoreService,
18} from "../../sources/source-store";
19import {
20 RuntimeSourceCatalogSyncService,
21} from "./sync";
13 22
14const shouldReconcileSource = (source: Source): boolean =>23const shouldReconcileSource = (source: Source): boolean =>
15 source.enabled24 source.enabled
@@ -17,23 +26,23 @@ const shouldReconcileSource = (source: Source): boolean => · 1 unmodified line
17 && getSourceAdapterForSource(source).catalogKind !== "internal";26 && getSourceAdapterForSource(source).catalogKind !== "internal";
18 27
19export const reconcileMissingSourceCatalogArtifacts = (input: {28export const reconcileMissingSourceCatalogArtifacts = (input: {
20 workspaceId: WorkspaceId;29 scopeId: ScopeId;
21 actorAccountId?: AccountId | null;30 actorScopeId?: ScopeId | null;
22}): Effect.Effect<31}): Effect.Effect<
23 void,32 void,
24 Error,33 Error,
25 | RuntimeLocalWorkspaceService34 | RuntimeLocalScopeService
26 | SourceArtifactStore35 | SourceArtifactStore
27 | RuntimeSourceStoreService36 | RuntimeSourceStoreService
28 | RuntimeSourceCatalogSyncService37 | RuntimeSourceCatalogSyncService
29> =>38> =>
30 Effect.gen(function* () {39 Effect.gen(function* () {
31 const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService;40 const runtimeLocalScope = yield* RuntimeLocalScopeService;
32 const sourceStore = yield* RuntimeSourceStoreService;41 const sourceStore = yield* RuntimeSourceStoreService;
33 const sourceArtifactStore = yield* SourceArtifactStore;42 const sourceArtifactStore = yield* SourceArtifactStore;
34 const sourceCatalogSync = yield* RuntimeSourceCatalogSyncService;43 const sourceCatalogSync = yield* RuntimeSourceCatalogSyncService;
35 const sources = yield* sourceStore.loadSourcesInWorkspace(input.workspaceId, {44 const sources = yield* sourceStore.loadSourcesInScope(input.scopeId, {
36 actorAccountId: input.actorAccountId,45 actorScopeId: input.actorScopeId,
37 });46 });
38 47
39 for (const source of sources) {48 for (const source of sources) {
@@ -50,7 +59,7 @@ export const reconcileMissingSourceCatalogArtifacts = (input: { · 10 unmodified lines
50 59
51 yield* sourceCatalogSync.sync({60 yield* sourceCatalogSync.sync({
52 source,61 source,
53 actorAccountId: input.actorAccountId,62 actorScopeId: input.actorScopeId,
54 }).pipe(63 }).pipe(
55 Effect.catchAll(() => Effect.void),64 Effect.catchAll(() => Effect.void),
56 );65 );
@@ -58,7 +67,7 @@ export const reconcileMissingSourceCatalogArtifacts = (input: { · 1 unmodified line
58 }).pipe(67 }).pipe(
59 Effect.withSpan("source.catalog.reconcile_missing", {68 Effect.withSpan("source.catalog.reconcile_missing", {
60 attributes: {69 attributes: {
61 "executor.workspace.id": input.workspaceId,70 "executor.scope.id": input.scopeId,
62 },71 },
63 }),72 }),
64 );73 );
packages/platform/sdk/src/runtime/catalog/source/runtime.test.ts+12−5
@@ -1,4 +1,8 @@
1import { describe, expect, it } from "@effect/vitest";1import {
2 describe,
3 expect,
4 it,
5} from "@effect/vitest";
2import * as Effect from "effect/Effect";6import * as Effect from "effect/Effect";
3 7
4import type {8import type {
@@ -10,7 +14,7 @@ import { · 5 unmodified lines
10 SourceCatalogIdSchema,14 SourceCatalogIdSchema,
11 SourceCatalogRevisionIdSchema,15 SourceCatalogRevisionIdSchema,
12 SourceIdSchema,16 SourceIdSchema,
13 WorkspaceIdSchema,17 ScopeIdSchema,
14} from "#schema";18} from "#schema";
15 19
16import {20import {
@@ -27,7 +31,10 @@ import { · 10 unmodified lines
27 ScopeIdSchema,31 ScopeIdSchema,
28 ShapeSymbolIdSchema,32 ShapeSymbolIdSchema,
29} from "@executor/ir/ids";33} from "@executor/ir/ids";
30import type { CatalogV1, ProvenanceRef } from "@executor/ir/model";34import type {
35 CatalogV1,
36 ProvenanceRef,
37} from "@executor/ir/model";
31import {38import {
32 createCatalogTypeProjector,39 createCatalogTypeProjector,
33 formatTypeNameSegment,40 formatTypeNameSegment,
@@ -274,7 +281,7 @@ const createLoadedCatalog = (options?: { · 240 unmodified lines
274 281
275 const source = {282 const source = {
276 id: SourceIdSchema.make("src_linear"),283 id: SourceIdSchema.make("src_linear"),
277 workspaceId: WorkspaceIdSchema.make("ws_linear"),284 scopeId: ScopeIdSchema.make("ws_linear"),
278 name: "Linear",285 name: "Linear",
279 kind: "graphql-schema",286 kind: "graphql-schema",
280 endpoint: "https://api.linear.app/graphql",287 endpoint: "https://api.linear.app/graphql",
@@ -294,7 +301,7 @@ const createLoadedCatalog = (options?: { · 13 unmodified lines
294 301
295 const sourceRecord = {302 const sourceRecord = {
296 id: source.id,303 id: source.id,
297 workspaceId: source.workspaceId,304 scopeId: source.scopeId,
298 catalogId: SourceCatalogIdSchema.make("catalog_linear"),305 catalogId: SourceCatalogIdSchema.make("catalog_linear"),
299 catalogRevisionId: SourceCatalogRevisionIdSchema.make("catalog_revision_linear"),306 catalogRevisionId: SourceCatalogRevisionIdSchema.make("catalog_revision_linear"),
300 name: source.name,307 name: source.name,
packages/platform/sdk/src/runtime/catalog/source/runtime.ts+65−56
@@ -3,11 +3,10 @@ import { · 2 unmodified lines
3 type ToolDescriptor as CatalogToolDescriptor,3 type ToolDescriptor as CatalogToolDescriptor,
4} from "@executor/codemode-core";4} from "@executor/codemode-core";
5import type {5import type {
6 AccountId,6 ScopeId,
7 Source,7 Source,
8 StoredSourceRecord,8 StoredSourceRecord,
9 StoredSourceCatalogRevisionRecord,9 StoredSourceCatalogRevisionRecord,
10 WorkspaceId,
11} from "#schema";10} from "#schema";
12import * as Context from "effect/Context";11import * as Context from "effect/Context";
13import * as Effect from "effect/Effect";12import * as Effect from "effect/Effect";
@@ -19,7 +18,9 @@ import { · 5 unmodified lines
19 projectCatalogForAgentSdk,18 projectCatalogForAgentSdk,
20 type ProjectedCatalog,19 type ProjectedCatalog,
21} from "@executor/ir/catalog";20} from "@executor/ir/catalog";
22import type { ShapeSymbolId } from "@executor/ir/ids";21import type {
22 ShapeSymbolId,
23} from "@executor/ir/ids";
23import type {24import type {
24 Capability,25 Capability,
25 CatalogSnapshotV1,26 CatalogSnapshotV1,
@@ -27,7 +28,9 @@ import type { · 1 unmodified line
27 Executable,28 Executable,
28 ShapeSymbol,29 ShapeSymbol,
29} from "@executor/ir/model";30} from "@executor/ir/model";
30import { LocalSourceArtifactMissingError } from "../../workspace-errors";31import {
32 LocalSourceArtifactMissingError,
33} from "../../scope-errors";
31import {34import {
32 createCatalogTypeProjector,35 createCatalogTypeProjector,
33 documentationComment,36 documentationComment,
@@ -36,21 +39,27 @@ import { · 2 unmodified lines
36 shapeAllowsOmittedArgs,39 shapeAllowsOmittedArgs,
37 type CatalogTypeProjector,40 type CatalogTypeProjector,
38} from "../catalog-typescript";41} from "../catalog-typescript";
39import { formatWithPrettier } from "../prettier-format";
40import {42import {
41 RuntimeLocalWorkspaceService,43 formatWithPrettier,
42 type RuntimeLocalWorkspaceState,44} from "../prettier-format";
43} from "../../workspace/runtime-context";45import {
44import type { LocalSourceArtifact } from "../../source-artifacts";46 RuntimeLocalScopeService,
47 type RuntimeLocalScopeState,
48} from "../../scope/runtime-context";
49import type {
50 LocalSourceArtifact,
51} from "../../source-artifacts";
45import {52import {
46 SourceArtifactStore,53 SourceArtifactStore,
47 type SourceArtifactStoreShape,54 type SourceArtifactStoreShape,
48} from "../../workspace/storage";55} from "../../scope/storage";
49import {56import {
50 RuntimeSourceStoreService,57 RuntimeSourceStoreService,
51 type RuntimeSourceStore,58 type RuntimeSourceStore,
52} from "../../sources/source-store";59} from "../../sources/source-store";
53import { runtimeEffectError } from "../../effect-errors";60import {
61 runtimeEffectError,
62} from "../../effect-errors";
54 63
55type CatalogImportMetadata = CatalogSnapshotV1["import"];64type CatalogImportMetadata = CatalogSnapshotV1["import"];
56 65
@@ -644,7 +653,7 @@ const sourceRecordFromCatalogArtifact = (input: { · 587 unmodified lines
644 };653 };
645}): StoredSourceRecord => ({654}): StoredSourceRecord => ({
646 id: input.source.id,655 id: input.source.id,
647 workspaceId: input.source.workspaceId,656 scopeId: input.source.scopeId,
648 catalogId: input.artifact.catalogId,657 catalogId: input.artifact.catalogId,
649 catalogRevisionId: input.artifact.revision.id,658 catalogRevisionId: input.artifact.revision.id,
650 name: input.source.name,659 name: input.source.name,
@@ -663,23 +672,23 @@ const sourceRecordFromCatalogArtifact = (input: { · 12 unmodified lines
663 672
664type RuntimeSourceCatalogStoreShape = {673type RuntimeSourceCatalogStoreShape = {
665 loadWorkspaceSourceCatalogs: (input: {674 loadWorkspaceSourceCatalogs: (input: {
666 workspaceId: WorkspaceId;675 scopeId: ScopeId;
667 actorAccountId?: AccountId | null;676 actorScopeId?: ScopeId | null;
668 }) => Effect.Effect<readonly LoadedSourceCatalog[], Error, never>;677 }) => Effect.Effect<readonly LoadedSourceCatalog[], Error, never>;
669 loadSourceWithCatalog: (input: {678 loadSourceWithCatalog: (input: {
670 workspaceId: WorkspaceId;679 scopeId: ScopeId;
671 sourceId: Source["id"];680 sourceId: Source["id"];
672 actorAccountId?: AccountId | null;681 actorScopeId?: ScopeId | null;
673 }) => Effect.Effect<LoadedSourceCatalog, Error | LocalSourceArtifactMissingError, never>;682 }) => Effect.Effect<LoadedSourceCatalog, Error | LocalSourceArtifactMissingError, never>;
674 loadWorkspaceSourceCatalogToolIndex: (input: {683 loadWorkspaceSourceCatalogToolIndex: (input: {
675 workspaceId: WorkspaceId;684 scopeId: ScopeId;
676 actorAccountId?: AccountId | null;685 actorScopeId?: ScopeId | null;
677 includeSchemas: boolean;686 includeSchemas: boolean;
678 }) => Effect.Effect<readonly LoadedSourceCatalogToolIndexEntry[], Error, never>;687 }) => Effect.Effect<readonly LoadedSourceCatalogToolIndexEntry[], Error, never>;
679 loadWorkspaceSourceCatalogToolByPath: (input: {688 loadWorkspaceSourceCatalogToolByPath: (input: {
680 workspaceId: WorkspaceId;689 scopeId: ScopeId;
681 path: string;690 path: string;
682 actorAccountId?: AccountId | null;691 actorScopeId?: ScopeId | null;
683 includeSchemas: boolean;692 includeSchemas: boolean;
684 }) => Effect.Effect<LoadedSourceCatalogToolIndexEntry | null, Error, never>;693 }) => Effect.Effect<LoadedSourceCatalogToolIndexEntry | null, Error, never>;
685};694};
@@ -691,25 +700,25 @@ export class RuntimeSourceCatalogStoreService extends Context.Tag( · 5 unmodified lines
691)<RuntimeSourceCatalogStoreService, RuntimeSourceCatalogStoreShape>() {}700)<RuntimeSourceCatalogStoreService, RuntimeSourceCatalogStoreShape>() {}
692 701
693type RuntimeSourceCatalogStoreDeps = {702type RuntimeSourceCatalogStoreDeps = {
694 runtimeLocalWorkspace: RuntimeLocalWorkspaceState;703 runtimeLocalScope: RuntimeLocalScopeState;
695 sourceStore: RuntimeSourceStore;704 sourceStore: RuntimeSourceStore;
696 sourceArtifactStore: SourceArtifactStoreShape;705 sourceArtifactStore: SourceArtifactStoreShape;
697};706};
698 707
699type SourceCatalogRuntimeServices =708type SourceCatalogRuntimeServices =
700 | RuntimeLocalWorkspaceService709 | RuntimeLocalScopeService
701 | RuntimeSourceStoreService710 | RuntimeSourceStoreService
702 | SourceArtifactStore;711 | SourceArtifactStore;
703 712
704const ensureRuntimeCatalogWorkspace = (713const ensureRuntimeCatalogWorkspace = (
705 deps: RuntimeSourceCatalogStoreDeps,714 deps: RuntimeSourceCatalogStoreDeps,
706 workspaceId: WorkspaceId,715 scopeId: ScopeId,
707) =>716) =>
708 Effect.gen(function* () {717 Effect.gen(function* () {
709 if (deps.runtimeLocalWorkspace.installation.workspaceId !== workspaceId) {718 if (deps.runtimeLocalScope.installation.scopeId !== scopeId) {
710 return yield* Effect.fail(719 return yield* Effect.fail(
711 runtimeEffectError("catalog/source/runtime",720 runtimeEffectError("catalog/source/runtime",
712 `Runtime local workspace mismatch: expected ${workspaceId}, got ${deps.runtimeLocalWorkspace.installation.workspaceId}`,721 `Runtime local scope mismatch: expected ${scopeId}, got ${deps.runtimeLocalScope.installation.scopeId}`,
713 ),722 ),
714 );723 );
715 }724 }
@@ -723,15 +732,15 @@ const buildSnapshotFromArtifact = (input: { · 7 unmodified lines
723};732};
724 733
725const loadWorkspaceSourceCatalogsWithDeps = (deps: RuntimeSourceCatalogStoreDeps, input: {734const loadWorkspaceSourceCatalogsWithDeps = (deps: RuntimeSourceCatalogStoreDeps, input: {
726 workspaceId: WorkspaceId;735 scopeId: ScopeId;
727 actorAccountId?: AccountId | null;736 actorScopeId?: ScopeId | null;
728}): Effect.Effect<readonly LoadedSourceCatalog[], Error, never> =>737}): Effect.Effect<readonly LoadedSourceCatalog[], Error, never> =>
729 Effect.gen(function* () {738 Effect.gen(function* () {
730 yield* ensureRuntimeCatalogWorkspace(deps, input.workspaceId);739 yield* ensureRuntimeCatalogWorkspace(deps, input.scopeId);
731 const sources = yield* deps.sourceStore.loadSourcesInWorkspace(740 const sources = yield* deps.sourceStore.loadSourcesInScope(
732 input.workspaceId,741 input.scopeId,
733 {742 {
734 actorAccountId: input.actorAccountId,743 actorScopeId: input.actorScopeId,
735 },744 },
736 );745 );
737 746
@@ -773,16 +782,16 @@ const loadWorkspaceSourceCatalogsWithDeps = (deps: RuntimeSourceCatalogStoreDeps · 35 unmodified lines
773 });782 });
774 783
775const loadSourceWithCatalogWithDeps = (deps: RuntimeSourceCatalogStoreDeps, input: {784const loadSourceWithCatalogWithDeps = (deps: RuntimeSourceCatalogStoreDeps, input: {
776 workspaceId: WorkspaceId;785 scopeId: ScopeId;
777 sourceId: Source["id"];786 sourceId: Source["id"];
778 actorAccountId?: AccountId | null;787 actorScopeId?: ScopeId | null;
779}): Effect.Effect<LoadedSourceCatalog, Error | LocalSourceArtifactMissingError, never> =>788}): Effect.Effect<LoadedSourceCatalog, Error | LocalSourceArtifactMissingError, never> =>
780 Effect.gen(function* () {789 Effect.gen(function* () {
781 yield* ensureRuntimeCatalogWorkspace(deps, input.workspaceId);790 yield* ensureRuntimeCatalogWorkspace(deps, input.scopeId);
782 const source = yield* deps.sourceStore.loadSourceById({791 const source = yield* deps.sourceStore.loadSourceById({
783 workspaceId: input.workspaceId,792 scopeId: input.scopeId,
784 sourceId: input.sourceId,793 sourceId: input.sourceId,
785 actorAccountId: input.actorAccountId,794 actorScopeId: input.actorScopeId,
786 });795 });
787 const artifact = yield* deps.sourceArtifactStore.read({796 const artifact = yield* deps.sourceArtifactStore.read({
788 sourceId: source.id,797 sourceId: source.id,
@@ -819,17 +828,17 @@ const loadSourceWithCatalogWithDeps = (deps: RuntimeSourceCatalogStoreDeps, inpu · 30 unmodified lines
819 });828 });
820 829
821export const loadWorkspaceSourceCatalogs = (input: {830export const loadWorkspaceSourceCatalogs = (input: {
822 workspaceId: WorkspaceId;831 scopeId: ScopeId;
823 actorAccountId?: AccountId | null;832 actorScopeId?: ScopeId | null;
824}): Effect.Effect<readonly LoadedSourceCatalog[], Error, SourceCatalogRuntimeServices> =>833}): Effect.Effect<readonly LoadedSourceCatalog[], Error, SourceCatalogRuntimeServices> =>
825 Effect.gen(function* () {834 Effect.gen(function* () {
826 const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService;835 const runtimeLocalScope = yield* RuntimeLocalScopeService;
827 const sourceStore = yield* RuntimeSourceStoreService;836 const sourceStore = yield* RuntimeSourceStoreService;
828 const sourceArtifactStore = yield* SourceArtifactStore;837 const sourceArtifactStore = yield* SourceArtifactStore;
829 838
830 return yield* loadWorkspaceSourceCatalogsWithDeps(839 return yield* loadWorkspaceSourceCatalogsWithDeps(
831 {840 {
832 runtimeLocalWorkspace,841 runtimeLocalScope,
833 sourceStore,842 sourceStore,
834 sourceArtifactStore,843 sourceArtifactStore,
835 },844 },
@@ -838,22 +847,22 @@ export const loadWorkspaceSourceCatalogs = (input: { · 2 unmodified lines
838 });847 });
839 848
840export const loadSourceWithCatalog = (input: {849export const loadSourceWithCatalog = (input: {
841 workspaceId: WorkspaceId;850 scopeId: ScopeId;
842 sourceId: Source["id"];851 sourceId: Source["id"];
843 actorAccountId?: AccountId | null;852 actorScopeId?: ScopeId | null;
844}): Effect.Effect<853}): Effect.Effect<
845 LoadedSourceCatalog,854 LoadedSourceCatalog,
846 Error | LocalSourceArtifactMissingError,855 Error | LocalSourceArtifactMissingError,
847 SourceCatalogRuntimeServices856 SourceCatalogRuntimeServices
848> =>857> =>
849 Effect.gen(function* () {858 Effect.gen(function* () {
850 const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService;859 const runtimeLocalScope = yield* RuntimeLocalScopeService;
851 const sourceStore = yield* RuntimeSourceStoreService;860 const sourceStore = yield* RuntimeSourceStoreService;
852 const sourceArtifactStore = yield* SourceArtifactStore;861 const sourceArtifactStore = yield* SourceArtifactStore;
853 862
854 return yield* loadSourceWithCatalogWithDeps(863 return yield* loadSourceWithCatalogWithDeps(
855 {864 {
856 runtimeLocalWorkspace,865 runtimeLocalScope,
857 sourceStore,866 sourceStore,
858 sourceArtifactStore,867 sourceArtifactStore,
859 },868 },
@@ -1096,8 +1105,8 @@ export const expandCatalogToolByPath = (input: { · 236 unmodified lines
1096 );1105 );
1097 1106
1098export const loadWorkspaceSourceCatalogToolIndex = (input: {1107export const loadWorkspaceSourceCatalogToolIndex = (input: {
1099 workspaceId: WorkspaceId;1108 scopeId: ScopeId;
1100 actorAccountId?: AccountId | null;1109 actorScopeId?: ScopeId | null;
1101 includeSchemas: boolean;1110 includeSchemas: boolean;
1102}): Effect.Effect<1111}): Effect.Effect<
1103 readonly LoadedSourceCatalogToolIndexEntry[],1112 readonly LoadedSourceCatalogToolIndexEntry[],
@@ -1106,8 +1115,8 @@ export const loadWorkspaceSourceCatalogToolIndex = (input: { · 2 unmodified lines
1106> =>1115> =>
1107 Effect.gen(function* () {1116 Effect.gen(function* () {
1108 const catalogs = yield* loadWorkspaceSourceCatalogs({1117 const catalogs = yield* loadWorkspaceSourceCatalogs({
1109 workspaceId: input.workspaceId,1118 scopeId: input.scopeId,
1110 actorAccountId: input.actorAccountId,1119 actorScopeId: input.actorScopeId,
1111 });1120 });
1112 const tools = yield* expandCatalogTools({1121 const tools = yield* expandCatalogTools({
1113 catalogs,1122 catalogs,
@@ -1129,9 +1138,9 @@ export const loadWorkspaceSourceCatalogToolIndex = (input: { · 15 unmodified lines
1129 });1138 });
1130 1139
1131export const loadWorkspaceSourceCatalogToolByPath = (input: {1140export const loadWorkspaceSourceCatalogToolByPath = (input: {
1132 workspaceId: WorkspaceId;1141 scopeId: ScopeId;
1133 path: string;1142 path: string;
1134 actorAccountId?: AccountId | null;1143 actorScopeId?: ScopeId | null;
1135 includeSchemas: boolean;1144 includeSchemas: boolean;
1136}): Effect.Effect<1145}): Effect.Effect<
1137 LoadedSourceCatalogToolIndexEntry | null,1146 LoadedSourceCatalogToolIndexEntry | null,
@@ -1140,8 +1149,8 @@ export const loadWorkspaceSourceCatalogToolByPath = (input: { · 2 unmodified lines
1140> =>1149> =>
1141 Effect.gen(function* () {1150 Effect.gen(function* () {
1142 const catalogs = yield* loadWorkspaceSourceCatalogs({1151 const catalogs = yield* loadWorkspaceSourceCatalogs({
1143 workspaceId: input.workspaceId,1152 scopeId: input.scopeId,
1144 actorAccountId: input.actorAccountId,1153 actorScopeId: input.actorScopeId,
1145 });1154 });
1146 const tool = yield* expandCatalogToolByPath({1155 const tool = yield* expandCatalogToolByPath({
1147 catalogs,1156 catalogs,
@@ -1168,12 +1177,12 @@ export const loadWorkspaceSourceCatalogToolByPath = (input: { · 20 unmodified lines
1168export const RuntimeSourceCatalogStoreLive = Layer.effect(1177export const RuntimeSourceCatalogStoreLive = Layer.effect(
1169 RuntimeSourceCatalogStoreService,1178 RuntimeSourceCatalogStoreService,
1170 Effect.gen(function* () {1179 Effect.gen(function* () {
1171 const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService;1180 const runtimeLocalScope = yield* RuntimeLocalScopeService;
1172 const sourceStore = yield* RuntimeSourceStoreService;1181 const sourceStore = yield* RuntimeSourceStoreService;
1173 const sourceArtifactStore = yield* SourceArtifactStore;1182 const sourceArtifactStore = yield* SourceArtifactStore;
1174 1183
1175 const deps: RuntimeSourceCatalogStoreDeps = {1184 const deps: RuntimeSourceCatalogStoreDeps = {
1176 runtimeLocalWorkspace,1185 runtimeLocalScope,
1177 sourceStore,1186 sourceStore,
1178 sourceArtifactStore,1187 sourceArtifactStore,
1179 };1188 };
@@ -1185,13 +1194,13 @@ export const RuntimeSourceCatalogStoreLive = Layer.effect( · 5 unmodified lines
1185 loadSourceWithCatalogWithDeps(deps, input),1194 loadSourceWithCatalogWithDeps(deps, input),
1186 loadWorkspaceSourceCatalogToolIndex: (input) =>1195 loadWorkspaceSourceCatalogToolIndex: (input) =>
1187 loadWorkspaceSourceCatalogToolIndex(input).pipe(1196 loadWorkspaceSourceCatalogToolIndex(input).pipe(
1188 Effect.provideService(RuntimeLocalWorkspaceService, runtimeLocalWorkspace),1197 Effect.provideService(RuntimeLocalScopeService, runtimeLocalScope),
1189 Effect.provideService(RuntimeSourceStoreService, sourceStore),1198 Effect.provideService(RuntimeSourceStoreService, sourceStore),
1190 Effect.provideService(SourceArtifactStore, sourceArtifactStore),1199 Effect.provideService(SourceArtifactStore, sourceArtifactStore),
1191 ),1200 ),
1192 loadWorkspaceSourceCatalogToolByPath: (input) =>1201 loadWorkspaceSourceCatalogToolByPath: (input) =>
1193 loadWorkspaceSourceCatalogToolByPath(input).pipe(1202 loadWorkspaceSourceCatalogToolByPath(input).pipe(
1194 Effect.provideService(RuntimeLocalWorkspaceService, runtimeLocalWorkspace),1203 Effect.provideService(RuntimeLocalScopeService, runtimeLocalScope),
1195 Effect.provideService(RuntimeSourceStoreService, sourceStore),1204 Effect.provideService(RuntimeSourceStoreService, sourceStore),
1196 Effect.provideService(SourceArtifactStore, sourceArtifactStore),1205 Effect.provideService(SourceArtifactStore, sourceArtifactStore),
1197 ),1206 ),
packages/platform/sdk/src/runtime/catalog/source/snapshot.test.ts+21−7
@@ -1,20 +1,34 @@
1import { describe, expect, it } from "@effect/vitest";1import {
2 describe,
3 expect,
4 it,
5} from "@effect/vitest";
2 6
3import type { Source } from "#schema";7import type {
8 Source,
9} from "#schema";
4 10
5import {11import {
6 buildGraphqlToolPresentation,12 buildGraphqlToolPresentation,
7 compileGraphqlToolDefinitions,13 compileGraphqlToolDefinitions,
8 type GraphqlToolManifest,14 type GraphqlToolManifest,
9} from "@executor/source-graphql";15} from "@executor/source-graphql";
10import { createGoogleDiscoveryCatalogSnapshot } from "@executor/source-google-discovery";16import {
11import { createGraphqlCatalogSnapshot } from "@executor/source-graphql";17 createGoogleDiscoveryCatalogSnapshot,
12import { createMcpCatalogSnapshot } from "@executor/source-mcp";18} from "@executor/source-google-discovery";
13import { createOpenApiCatalogSnapshot } from "@executor/source-openapi";19import {
20 createGraphqlCatalogSnapshot,
21} from "@executor/source-graphql";
22import {
23 createMcpCatalogSnapshot,
24} from "@executor/source-mcp";
25import {
26 createOpenApiCatalogSnapshot,
27} from "@executor/source-openapi";
14 28
15const baseSource: Source = {29const baseSource: Source = {
16 id: "src_calendar" as Source["id"],30 id: "src_calendar" as Source["id"],
17 workspaceId: "ws_test" as Source["workspaceId"],31 scopeId: "ws_test" as Source["scopeId"],
18 name: "Calendar",32 name: "Calendar",
19 kind: "openapi",33 kind: "openapi",
20 endpoint: "https://api.example.test",34 endpoint: "https://api.example.test",
packages/platform/sdk/src/runtime/catalog/source/sync.ts+53−45
@@ -1,26 +1,28 @@
1import type {1import type {
2 AccountId,2 ScopeId,
3 Source,3 Source,
4 SourceStatus,4 SourceStatus,
5} from "#schema";5} from "#schema";
6import type { McpToolManifest } from "@executor/source-mcp";6import type {
7 McpToolManifest,
8} from "@executor/source-mcp";
7import * as Context from "effect/Context";9import * as Context from "effect/Context";
8import * as Effect from "effect/Effect";10import * as Effect from "effect/Effect";
9import * as Layer from "effect/Layer";11import * as Layer from "effect/Layer";
10 12
11import {13import {
12 RuntimeLocalWorkspaceService,14 RuntimeLocalScopeService,
13 type RuntimeLocalWorkspaceState,15 type RuntimeLocalScopeState,
14} from "../../workspace/runtime-context";16} from "../../scope/runtime-context";
15import {17import {
16 SourceArtifactStore,18 SourceArtifactStore,
17 type SourceArtifactStoreShape,19 type SourceArtifactStoreShape,
18 WorkspaceStateStore,20 ScopeStateStore,
19 type WorkspaceStateStoreShape,21 type ScopeStateStoreShape,
20} from "../../workspace/storage";22} from "../../scope/storage";
21import {23import {
22 type LocalWorkspaceState,24 type LocalScopeState,
23} from "../../workspace-state";25} from "../../scope-state";
24import {26import {
25 RuntimeSourceAuthMaterialService,27 RuntimeSourceAuthMaterialService,
26} from "../../auth/source-auth-material";28} from "../../auth/source-auth-material";
@@ -30,13 +32,19 @@ import { · 3 unmodified lines
30import {32import {
31 catalogSyncResultFromMcpManifest,33 catalogSyncResultFromMcpManifest,
32} from "@executor/source-mcp";34} from "@executor/source-mcp";
33import { SecretMaterialResolverService } from "../../workspace/secret-material-providers";35import {
34import { snapshotFromSourceCatalogSyncResult } from "@executor/source-core";36 SecretMaterialResolverService,
37} from "../../scope/secret-material-providers";
38import {
39 snapshotFromSourceCatalogSyncResult,
40} from "@executor/source-core";
35import {41import {
36 SourceTypeDeclarationsRefresherService,42 SourceTypeDeclarationsRefresherService,
37 type SourceTypeDeclarationsRefresherShape,43 type SourceTypeDeclarationsRefresherShape,
38} from "./type-declarations";44} from "./type-declarations";
39import { runtimeEffectError } from "../../effect-errors";45import {
46 runtimeEffectError,
47} from "../../effect-errors";
40 48
41const shouldIndexSource = (source: Source): boolean =>49const shouldIndexSource = (source: Source): boolean =>
42 source.enabled50 source.enabled
@@ -44,8 +52,8 @@ const shouldIndexSource = (source: Source): boolean => · 1 unmodified line
44 && getSourceAdapterForSource(source).catalogKind !== "internal";52 && getSourceAdapterForSource(source).catalogKind !== "internal";
45 53
46type RuntimeSourceCatalogSyncDeps = {54type RuntimeSourceCatalogSyncDeps = {
47 runtimeLocalWorkspace: RuntimeLocalWorkspaceState;55 runtimeLocalScope: RuntimeLocalScopeState;
48 workspaceStateStore: WorkspaceStateStoreShape;56 scopeStateStore: ScopeStateStoreShape;
49 sourceArtifactStore: SourceArtifactStoreShape;57 sourceArtifactStore: SourceArtifactStoreShape;
50 sourceTypeDeclarationsRefresher: SourceTypeDeclarationsRefresherShape;58 sourceTypeDeclarationsRefresher: SourceTypeDeclarationsRefresherShape;
51 resolveSecretMaterial: Effect.Effect.Success<typeof SecretMaterialResolverService>;59 resolveSecretMaterial: Effect.Effect.Success<typeof SecretMaterialResolverService>;
@@ -53,8 +61,8 @@ type RuntimeSourceCatalogSyncDeps = { · 1 unmodified line
53};61};
54 62
55type SourceCatalogSyncServices =63type SourceCatalogSyncServices =
56 | RuntimeLocalWorkspaceService64 | RuntimeLocalScopeService
57 | WorkspaceStateStore65 | ScopeStateStore
58 | SourceArtifactStore66 | SourceArtifactStore
59 | SourceTypeDeclarationsRefresherService67 | SourceTypeDeclarationsRefresherService
60 | RuntimeSourceAuthMaterialService68 | RuntimeSourceAuthMaterialService
@@ -63,7 +71,7 @@ type SourceCatalogSyncServices = · 2 unmodified lines
63export type RuntimeSourceCatalogSyncShape = {71export type RuntimeSourceCatalogSyncShape = {
64 sync: (input: {72 sync: (input: {
65 source: Source;73 source: Source;
66 actorAccountId?: AccountId | null;74 actorScopeId?: ScopeId | null;
67 }) => Effect.Effect<void, Error, never>;75 }) => Effect.Effect<void, Error, never>;
68 persistMcpCatalogSnapshotFromManifest: (input: {76 persistMcpCatalogSnapshotFromManifest: (input: {
69 source: Source;77 source: Source;
@@ -77,13 +85,13 @@ export class RuntimeSourceCatalogSyncService extends Context.Tag( · 7 unmodified lines
77 85
78const ensureRuntimeCatalogSyncWorkspace = (86const ensureRuntimeCatalogSyncWorkspace = (
79 deps: RuntimeSourceCatalogSyncDeps,87 deps: RuntimeSourceCatalogSyncDeps,
80 workspaceId: Source["workspaceId"],88 scopeId: Source["scopeId"],
81) =>89) =>
82 Effect.gen(function* () {90 Effect.gen(function* () {
83 if (deps.runtimeLocalWorkspace.installation.workspaceId !== workspaceId) {91 if (deps.runtimeLocalScope.installation.scopeId !== scopeId) {
84 return yield* Effect.fail(92 return yield* Effect.fail(
85 runtimeEffectError("catalog/source/sync", 93 runtimeEffectError("catalog/source/sync",
86 `Runtime local workspace mismatch: expected ${workspaceId}, got ${deps.runtimeLocalWorkspace.installation.workspaceId}`,94 `Runtime local scope mismatch: expected ${scopeId}, got ${deps.runtimeLocalScope.installation.scopeId}`,
87 ),95 ),
88 );96 );
89 }97 }
@@ -93,16 +101,16 @@ const syncSourceCatalogWithDeps = ( · 3 unmodified lines
93 deps: RuntimeSourceCatalogSyncDeps,101 deps: RuntimeSourceCatalogSyncDeps,
94 input: {102 input: {
95 source: Source;103 source: Source;
96 actorAccountId?: AccountId | null;104 actorScopeId?: ScopeId | null;
97 },105 },
98): Effect.Effect<void, Error, never> =>106): Effect.Effect<void, Error, never> =>
99 Effect.gen(function* () {107 Effect.gen(function* () {
100 yield* ensureRuntimeCatalogSyncWorkspace(deps, input.source.workspaceId);108 yield* ensureRuntimeCatalogSyncWorkspace(deps, input.source.scopeId);
101 109
102 if (!shouldIndexSource(input.source)) {110 if (!shouldIndexSource(input.source)) {
103 const state = yield* deps.workspaceStateStore.load();111 const state = yield* deps.scopeStateStore.load();
104 const existingSourceState = state.sources[input.source.id];112 const existingSourceState = state.sources[input.source.id];
105 const nextState: LocalWorkspaceState = {113 const nextState: LocalScopeState = {
106 ...state,114 ...state,
107 sources: {115 sources: {
108 ...state.sources,116 ...state.sources,
@@ -115,7 +123,7 @@ const syncSourceCatalogWithDeps = ( · 6 unmodified lines
115 },123 },
116 },124 },
117 };125 };
118 yield* deps.workspaceStateStore.write({126 yield* deps.scopeStateStore.write({
119 state: nextState,127 state: nextState,
120 });128 });
121 yield* deps.sourceTypeDeclarationsRefresher.refreshSourceInBackground({129 yield* deps.sourceTypeDeclarationsRefresher.refreshSourceInBackground({
@@ -133,7 +141,7 @@ const syncSourceCatalogWithDeps = ( · 11 unmodified lines
133 deps.sourceAuthMaterialService.resolve({141 deps.sourceAuthMaterialService.resolve({
134 source: input.source,142 source: input.source,
135 slot,143 slot,
136 actorAccountId: input.actorAccountId,144 actorScopeId: input.actorScopeId,
137 }),145 }),
138 });146 });
139 const snapshot = snapshotFromSourceCatalogSyncResult(syncResult);147 const snapshot = snapshotFromSourceCatalogSyncResult(syncResult);
@@ -145,9 +153,9 @@ const syncSourceCatalogWithDeps = ( · 5 unmodified lines
145 }),153 }),
146 });154 });
147 155
148 const state = yield* deps.workspaceStateStore.load();156 const state = yield* deps.scopeStateStore.load();
149 const existingSourceState = state.sources[input.source.id];157 const existingSourceState = state.sources[input.source.id];
150 const nextState: LocalWorkspaceState = {158 const nextState: LocalScopeState = {
151 ...state,159 ...state,
152 sources: {160 sources: {
153 ...state.sources,161 ...state.sources,
@@ -160,7 +168,7 @@ const syncSourceCatalogWithDeps = ( · 6 unmodified lines
160 },168 },
161 },169 },
162 };170 };
163 yield* deps.workspaceStateStore.write({171 yield* deps.scopeStateStore.write({
164 state: nextState,172 state: nextState,
165 });173 });
166 174
@@ -187,7 +195,7 @@ const persistMcpCatalogSnapshotFromManifestWithDeps = ( · 20 unmodified lines
187 },195 },
188): Effect.Effect<void, Error, never> =>196): Effect.Effect<void, Error, never> =>
189 Effect.gen(function* () {197 Effect.gen(function* () {
190 yield* ensureRuntimeCatalogSyncWorkspace(deps, input.source.workspaceId);198 yield* ensureRuntimeCatalogSyncWorkspace(deps, input.source.scopeId);
191 const syncResult = catalogSyncResultFromMcpManifest({199 const syncResult = catalogSyncResultFromMcpManifest({
192 source: input.source,200 source: input.source,
193 endpoint: input.source.endpoint,201 endpoint: input.source.endpoint,
@@ -211,11 +219,11 @@ const persistMcpCatalogSnapshotFromManifestWithDeps = ( · 17 unmodified lines
211 219
212export const syncSourceCatalog = (input: {220export const syncSourceCatalog = (input: {
213 source: Source;221 source: Source;
214 actorAccountId?: AccountId | null;222 actorScopeId?: ScopeId | null;
215}): Effect.Effect<void, Error, SourceCatalogSyncServices> =>223}): Effect.Effect<void, Error, SourceCatalogSyncServices> =>
216 Effect.gen(function* () {224 Effect.gen(function* () {
217 const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService;225 const runtimeLocalScope = yield* RuntimeLocalScopeService;
218 const workspaceStateStore = yield* WorkspaceStateStore;226 const scopeStateStore = yield* ScopeStateStore;
219 const sourceArtifactStore = yield* SourceArtifactStore;227 const sourceArtifactStore = yield* SourceArtifactStore;
220 const sourceTypeDeclarationsRefresher =228 const sourceTypeDeclarationsRefresher =
221 yield* SourceTypeDeclarationsRefresherService;229 yield* SourceTypeDeclarationsRefresherService;
@@ -224,8 +232,8 @@ export const syncSourceCatalog = (input: { · 2 unmodified lines
224 232
225 return yield* syncSourceCatalogWithDeps(233 return yield* syncSourceCatalogWithDeps(
226 {234 {
227 runtimeLocalWorkspace,235 runtimeLocalScope,
228 workspaceStateStore,236 scopeStateStore,
229 sourceArtifactStore,237 sourceArtifactStore,
230 sourceTypeDeclarationsRefresher,238 sourceTypeDeclarationsRefresher,
231 resolveSecretMaterial,239 resolveSecretMaterial,
@@ -233,7 +241,7 @@ export const syncSourceCatalog = (input: { · 1 unmodified line
233 },241 },
234 {242 {
235 source: input.source,243 source: input.source,
236 actorAccountId: input.actorAccountId,244 actorScopeId: input.actorScopeId,
237 },245 },
238 );246 );
239 });247 });
@@ -243,8 +251,8 @@ export const persistMcpCatalogSnapshotFromManifest = (input: { · 3 unmodified lines
243 manifest: McpToolManifest;251 manifest: McpToolManifest;
244}): Effect.Effect<void, Error, SourceCatalogSyncServices> =>252}): Effect.Effect<void, Error, SourceCatalogSyncServices> =>
245 Effect.gen(function* () {253 Effect.gen(function* () {
246 const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService;254 const runtimeLocalScope = yield* RuntimeLocalScopeService;
247 const workspaceStateStore = yield* WorkspaceStateStore;255 const scopeStateStore = yield* ScopeStateStore;
248 const sourceArtifactStore = yield* SourceArtifactStore;256 const sourceArtifactStore = yield* SourceArtifactStore;
249 const sourceTypeDeclarationsRefresher =257 const sourceTypeDeclarationsRefresher =
250 yield* SourceTypeDeclarationsRefresherService;258 yield* SourceTypeDeclarationsRefresherService;
@@ -253,8 +261,8 @@ export const persistMcpCatalogSnapshotFromManifest = (input: { · 2 unmodified lines
253 261
254 return yield* persistMcpCatalogSnapshotFromManifestWithDeps(262 return yield* persistMcpCatalogSnapshotFromManifestWithDeps(
255 {263 {
256 runtimeLocalWorkspace,264 runtimeLocalScope,
257 workspaceStateStore,265 scopeStateStore,
258 sourceArtifactStore,266 sourceArtifactStore,
259 sourceTypeDeclarationsRefresher,267 sourceTypeDeclarationsRefresher,
260 resolveSecretMaterial,268 resolveSecretMaterial,
@@ -267,8 +275,8 @@ export const persistMcpCatalogSnapshotFromManifest = (input: { · 6 unmodified lines
267export const RuntimeSourceCatalogSyncLive = Layer.effect(275export const RuntimeSourceCatalogSyncLive = Layer.effect(
268 RuntimeSourceCatalogSyncService,276 RuntimeSourceCatalogSyncService,
269 Effect.gen(function* () {277 Effect.gen(function* () {
270 const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService;278 const runtimeLocalScope = yield* RuntimeLocalScopeService;
271 const workspaceStateStore = yield* WorkspaceStateStore;279 const scopeStateStore = yield* ScopeStateStore;
272 const sourceArtifactStore = yield* SourceArtifactStore;280 const sourceArtifactStore = yield* SourceArtifactStore;
273 const sourceTypeDeclarationsRefresher =281 const sourceTypeDeclarationsRefresher =
274 yield* SourceTypeDeclarationsRefresherService;282 yield* SourceTypeDeclarationsRefresherService;
@@ -276,8 +284,8 @@ export const RuntimeSourceCatalogSyncLive = Layer.effect( · 1 unmodified line
276 const sourceAuthMaterialService = yield* RuntimeSourceAuthMaterialService;284 const sourceAuthMaterialService = yield* RuntimeSourceAuthMaterialService;
277 285
278 const deps: RuntimeSourceCatalogSyncDeps = {286 const deps: RuntimeSourceCatalogSyncDeps = {
279 runtimeLocalWorkspace,287 runtimeLocalScope,
280 workspaceStateStore,288 scopeStateStore,
281 sourceArtifactStore,289 sourceArtifactStore,
282 sourceTypeDeclarationsRefresher,290 sourceTypeDeclarationsRefresher,
283 resolveSecretMaterial,291 resolveSecretMaterial,
packages/platform/sdk/src/runtime/catalog/source/type-declarations.ts+6−2
@@ -1,5 +1,9 @@
1import type { Source } from "#schema";1import type {
2import type { CatalogSnapshotV1 } from "@executor/ir/model";2 Source,
3} from "#schema";
4import type {
5 CatalogSnapshotV1,
6} from "@executor/ir/model";
3import * as Context from "effect/Context";7import * as Context from "effect/Context";
4import * as Effect from "effect/Effect";8import * as Effect from "effect/Effect";
5 9
packages/platform/sdk/src/runtime/execution/http.test.ts+39−19
@@ -1,16 +1,36 @@
1import { tmpdir } from "node:os";1import {
2import { join } from "node:path";2 tmpdir,
3import { FileSystem } from "@effect/platform";3} from "node:os";
4import { NodeFileSystem } from "@effect/platform-node";4import {
5import { describe, expect, it } from "@effect/vitest";5 join,
6} from "node:path";
7import {
8 FileSystem,
9} from "@effect/platform";
10import {
11 NodeFileSystem,
12} from "@effect/platform-node";
13import {
14 describe,
15 expect,
16 it,
17} from "@effect/vitest";
6import * as Effect from "effect/Effect";18import * as Effect from "effect/Effect";
7import * as Schema from "effect/Schema";19import * as Schema from "effect/Schema";
8 20
9import { makeToolInvokerFromTools } from "@executor/codemode-core";21import {
10import { makeDenoSubprocessExecutor } from "@executor/runtime-deno-subprocess";22 makeToolInvokerFromTools,
23} from "@executor/codemode-core";
24import {
25 makeDenoSubprocessExecutor,
26} from "@executor/runtime-deno-subprocess";
11 27
12import { createLocalExecutorRuntime as createExecutorRuntime } from "../../../../sdk-file/src/index";28import {
13import { withExecutorApiClient } from "./test-http-client";29 createLocalExecutorRuntime as createExecutorRuntime,
30} from "../../../../sdk-file/src/index";
31import {
32 withExecutorApiClient,
33} from "./test-http-client";
14 34
15const makeExecutionResolver = () => {35const makeExecutionResolver = () => {
16 const toolInvoker = makeToolInvokerFromTools({36 const toolInvoker = makeToolInvokerFromTools({
@@ -41,22 +61,22 @@ const makeExecutionResolver = () => { · 24 unmodified lines
41const makeRuntime = Effect.acquireRelease(61const makeRuntime = Effect.acquireRelease(
42 Effect.gen(function* () {62 Effect.gen(function* () {
43 const fs = yield* FileSystem.FileSystem;63 const fs = yield* FileSystem.FileSystem;
44 const workspaceRoot = yield* fs.makeTempDirectory({64 const scopeRoot = yield* fs.makeTempDirectory({
45 directory: tmpdir(),65 directory: tmpdir(),
46 prefix: "executor-execution-http-",66 prefix: "executor-execution-http-",
47 });67 });
48 68
49 return {69 return {
50 workspaceRoot,70 scopeRoot,
51 homeConfigPath: join(workspaceRoot, ".executor-home.jsonc"),71 homeConfigPath: join(scopeRoot, ".executor-home.jsonc"),
52 homeStateDirectory: join(workspaceRoot, ".executor-home-state"),72 homeStateDirectory: join(scopeRoot, ".executor-home-state"),
53 };73 };
54 }).pipe(74 }).pipe(
55 Effect.provide(NodeFileSystem.layer),75 Effect.provide(NodeFileSystem.layer),
56 Effect.flatMap(({ workspaceRoot, homeConfigPath, homeStateDirectory }) =>76 Effect.flatMap(({ scopeRoot, homeConfigPath, homeStateDirectory }) =>
57 createExecutorRuntime({77 createExecutorRuntime({
58 localDataDir: ":memory:",78 localDataDir: ":memory:",
59 workspaceRoot,79 workspaceRoot: scopeRoot,
60 homeConfigPath,80 homeConfigPath,
61 homeStateDirectory,81 homeStateDirectory,
62 executionResolver: makeExecutionResolver(),82 executionResolver: makeExecutionResolver(),
@@ -75,12 +95,12 @@ describe("execution-http", () => { · 12 unmodified lines
75 const createExecution = yield* withExecutorApiClient(95 const createExecution = yield* withExecutorApiClient(
76 {96 {
77 runtime,97 runtime,
78 accountId: installation.accountId,98 actorScopeId: installation.actorScopeId,
79 },99 },
80 (client) =>100 (client) =>
81 client.executions.create({101 client.executions.create({
82 path: {102 path: {
83 workspaceId: installation.workspaceId,103 scopeId: installation.scopeId,
84 },104 },
85 payload: {105 payload: {
86 code: "return await tools.math.add({ a: 20, b: 22 });",106 code: "return await tools.math.add({ a: 20, b: 22 });",
@@ -95,12 +115,12 @@ describe("execution-http", () => { · 8 unmodified lines
95 const getExecution = yield* withExecutorApiClient(115 const getExecution = yield* withExecutorApiClient(
96 {116 {
97 runtime,117 runtime,
98 accountId: installation.accountId,118 actorScopeId: installation.actorScopeId,
99 },119 },
100 (client) =>120 (client) =>
101 client.executions.get({121 client.executions.get({
102 path: {122 path: {
103 workspaceId: installation.workspaceId,123 scopeId: installation.scopeId,
104 executionId: createExecution.execution.id,124 executionId: createExecution.execution.id,
105 },125 },
106 }),126 }),
packages/platform/sdk/src/runtime/execution/ir-execution.ts+21−8
@@ -1,11 +1,24 @@
1import type { AccountId, Source } from "#schema";1import type {
2import type { OnElicitation } from "@executor/codemode-core";2 ScopeId,
3 Source,
4} from "#schema";
5import type {
6 OnElicitation,
7} from "@executor/codemode-core";
3import * as Effect from "effect/Effect";8import * as Effect from "effect/Effect";
4 9
5import type { LoadedSourceCatalogToolIndexEntry } from "../catalog/source/runtime";10import type {
6import type { ResolvedSourceAuthMaterial } from "../auth/source-auth-material";11 LoadedSourceCatalogToolIndexEntry,
7import { getSourceAdapter } from "../sources/source-adapters";12} from "../catalog/source/runtime";
8import { runtimeEffectError } from "../effect-errors";13import type {
14 ResolvedSourceAuthMaterial,
15} from "../auth/source-auth-material";
16import {
17 getSourceAdapter,
18} from "../sources/source-adapters";
19import {
20 runtimeEffectError,
21} from "../effect-errors";
9 22
10export const invocationDescriptorFromTool = (input: {23export const invocationDescriptorFromTool = (input: {
11 tool: LoadedSourceCatalogToolIndexEntry;24 tool: LoadedSourceCatalogToolIndexEntry;
@@ -39,8 +52,8 @@ export const invocationDescriptorFromTool = (input: { · 27 unmodified lines
39});52});
40 53
41export const invokeIrTool = (input: {54export const invokeIrTool = (input: {
42 workspaceId: Source["workspaceId"];55 scopeId: Source["scopeId"];
43 accountId: AccountId;56 actorScopeId: ScopeId;
44 tool: LoadedSourceCatalogToolIndexEntry;57 tool: LoadedSourceCatalogToolIndexEntry;
45 auth: ResolvedSourceAuthMaterial;58 auth: ResolvedSourceAuthMaterial;
46 args: unknown;59 args: unknown;
packages/platform/sdk/src/runtime/execution/live.test.ts+8−2
@@ -1,6 +1,12 @@
1import { describe, expect, it } from "@effect/vitest";1import {
2 describe,
3 expect,
4 it,
5} from "@effect/vitest";
2 6
3import { sanitizePersistedElicitationResponse } from "./live";7import {
8 sanitizePersistedElicitationResponse,
9} from "./live";
4 10
5describe("live-execution", () => {11describe("live-execution", () => {
6 it("redacts secret refs from persisted interaction responses", () => {12 it("redacts secret refs from persisted interaction responses", () => {
packages/platform/sdk/src/runtime/execution/live.ts+6−2
@@ -2,7 +2,9 @@ import type { · 1 unmodified line
2 ElicitationResponse,2 ElicitationResponse,
3 OnElicitation,3 OnElicitation,
4} from "@executor/codemode-core";4} from "@executor/codemode-core";
5import { clearMcpConnectionPoolRun } from "@executor/source-mcp";5import {
6 clearMcpConnectionPoolRun,
7} from "@executor/source-mcp";
6import {8import {
7 ExecutionInteractionIdSchema,9 ExecutionInteractionIdSchema,
8 type Execution,10 type Execution,
@@ -13,7 +15,9 @@ import * as Deferred from "effect/Deferred"; · 4 unmodified lines
13import * as Effect from "effect/Effect";15import * as Effect from "effect/Effect";
14import * as Layer from "effect/Layer";16import * as Layer from "effect/Layer";
15 17
16import type { ExecutorStateStoreShape } from "../executor-state-store";18import type {
19 ExecutorStateStoreShape,
20} from "../executor-state-store";
17 21
18type VisibleExecutionState =22type VisibleExecutionState =
19 | "running"23 | "running"
packages/platform/sdk/src/runtime/execution/mcp-resume.test.ts+65−27
@@ -1,29 +1,67 @@
1import { randomUUID } from "node:crypto";1import {
2 2 randomUUID,
3import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";3} from "node:crypto";
4import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";4
5import { Client } from "@modelcontextprotocol/sdk/client/index.js";5import {
6import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";6 createMcpExpressApp,
7import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";7} from "@modelcontextprotocol/sdk/server/express.js";
8import { tmpdir } from "node:os";8import {
9import { join } from "node:path";9 McpServer,
10import { FileSystem } from "@effect/platform";10} from "@modelcontextprotocol/sdk/server/mcp.js";
11import { NodeFileSystem } from "@effect/platform-node";11import {
12import { describe, expect, it } from "@effect/vitest";12 Client,
13import { assertTrue } from "@effect/vitest/utils";13} from "@modelcontextprotocol/sdk/client/index.js";
14import {
15 StreamableHTTPClientTransport,
16} from "@modelcontextprotocol/sdk/client/streamableHttp.js";
17import {
18 StreamableHTTPServerTransport,
19} from "@modelcontextprotocol/sdk/server/streamableHttp.js";
20import {
21 tmpdir,
22} from "node:os";
23import {
24 join,
25} from "node:path";
26import {
27 FileSystem,
28} from "@effect/platform";
29import {
30 NodeFileSystem,
31} from "@effect/platform-node";
32import {
33 describe,
34 expect,
35 it,
36} from "@effect/vitest";
37import {
38 assertTrue,
39} from "@effect/vitest/utils";
14import * as Effect from "effect/Effect";40import * as Effect from "effect/Effect";
15import * as Option from "effect/Option";41import * as Option from "effect/Option";
16import { z } from "zod/v4";42import {
43 z,
44} from "zod/v4";
17 45
18import { makeToolInvokerFromTools } from "@executor/codemode-core";46import {
19import { discoverMcpToolsFromConnector } from "@executor/source-mcp";47 makeToolInvokerFromTools,
20import { makeDenoSubprocessExecutor } from "@executor/runtime-deno-subprocess";48} from "@executor/codemode-core";
49import {
50 discoverMcpToolsFromConnector,
51} from "@executor/source-mcp";
52import {
53 makeDenoSubprocessExecutor,
54} from "@executor/runtime-deno-subprocess";
21 55
22import {56import {
23 type ResolveExecutionEnvironment,57 type ResolveExecutionEnvironment,
24} from "../index";58} from "../index";
25import { createLocalExecutorRuntime as createExecutorRuntime } from "../../../../sdk-file/src/index";59import {
26import { withExecutorApiClient } from "./test-http-client";60 createLocalExecutorRuntime as createExecutorRuntime,
61} from "../../../../sdk-file/src/index";
62import {
63 withExecutorApiClient,
64} from "./test-http-client";
27 65
28type McpFormServer = {66type McpFormServer = {
29 endpoint: string;67 endpoint: string;
@@ -290,16 +328,16 @@ describe("execution-mcp-resume", () => { · 260 unmodified lines
290 Effect.gen(function* () {328 Effect.gen(function* () {
291 const mcpServer = yield* makeFormElicitationServer;329 const mcpServer = yield* makeFormElicitationServer;
292 const fs = yield* FileSystem.FileSystem;330 const fs = yield* FileSystem.FileSystem;
293 const workspaceRoot = yield* fs.makeTempDirectory({331 const scopeRoot = yield* fs.makeTempDirectory({
294 directory: tmpdir(),332 directory: tmpdir(),
295 prefix: "executor-execution-mcp-resume-",333 prefix: "executor-execution-mcp-resume-",
296 });334 });
297 const runtime = yield* Effect.acquireRelease(335 const runtime = yield* Effect.acquireRelease(
298 createExecutorRuntime({336 createExecutorRuntime({
299 localDataDir: ":memory:",337 localDataDir: ":memory:",
300 workspaceRoot,338 workspaceRoot: scopeRoot,
301 homeConfigPath: join(workspaceRoot, ".executor-home.jsonc"),339 homeConfigPath: join(scopeRoot, ".executor-home.jsonc"),
302 homeStateDirectory: join(workspaceRoot, ".executor-home-state"),340 homeStateDirectory: join(scopeRoot, ".executor-home-state"),
303 executionResolver: makeMcpExecutionResolver(mcpServer.endpoint),341 executionResolver: makeMcpExecutionResolver(mcpServer.endpoint),
304 }),342 }),
305 (runtime) => Effect.promise(() => runtime.close()).pipe(Effect.orDie),343 (runtime) => Effect.promise(() => runtime.close()).pipe(Effect.orDie),
@@ -310,12 +348,12 @@ describe("execution-mcp-resume", () => { · 4 unmodified lines
310 const created = yield* withExecutorApiClient(348 const created = yield* withExecutorApiClient(
311 {349 {
312 runtime,350 runtime,
313 accountId: installation.accountId,351 actorScopeId: installation.actorScopeId,
314 },352 },
315 (client) =>353 (client) =>
316 client.executions.create({354 client.executions.create({
317 path: {355 path: {
318 workspaceId: installation.workspaceId,356 scopeId: installation.scopeId,
319 },357 },
320 payload: {358 payload: {
321 code: 'return await tools.source.form.gated_echo({ value: "from-control-plane" });',359 code: 'return await tools.source.form.gated_echo({ value: "from-control-plane" });',
@@ -344,12 +382,12 @@ describe("execution-mcp-resume", () => { · 22 unmodified lines
344 const resumed = yield* withExecutorApiClient(382 const resumed = yield* withExecutorApiClient(
345 {383 {
346 runtime,384 runtime,
347 accountId: installation.accountId,385 actorScopeId: installation.actorScopeId,
348 },386 },
349 (client) =>387 (client) =>
350 client.executions.resume({388 client.executions.resume({
351 path: {389 path: {
352 workspaceId: installation.workspaceId,390 scopeId: installation.scopeId,
353 executionId: created.execution.id,391 executionId: created.execution.id,
354 },392 },
355 payload: {393 payload: {
packages/platform/sdk/src/runtime/execution/runtime-config.test.ts+36−20
@@ -1,20 +1,36 @@
1import { tmpdir } from "node:os";1import {
2import { join } from "node:path";2 tmpdir,
3import { FileSystem } from "@effect/platform";3} from "node:os";
4import { NodeFileSystem } from "@effect/platform-node";4import {
5import { describe, expect, it } from "@effect/vitest";5 join,
6} from "node:path";
7import {
8 FileSystem,
9} from "@effect/platform";
10import {
11 NodeFileSystem,
12} from "@effect/platform-node";
13import {
14 describe,
15 expect,
16 it,
17} from "@effect/vitest";
6import * as Effect from "effect/Effect";18import * as Effect from "effect/Effect";
7 19
8import { createLocalExecutorRuntime as createExecutorRuntime } from "../../../../sdk-file/src/index";20import {
9import { withExecutorApiClient } from "./test-http-client";21 createLocalExecutorRuntime as createExecutorRuntime,
22} from "../../../../sdk-file/src/index";
23import {
24 withExecutorApiClient,
25} from "./test-http-client";
10 26
11const writeProjectConfig = (27const writeProjectConfig = (
12 workspaceRoot: string,28 scopeRoot: string,
13 config: Record<string, unknown>,29 config: Record<string, unknown>,
14) =>30) =>
15 Effect.gen(function* () {31 Effect.gen(function* () {
16 const fs = yield* FileSystem.FileSystem;32 const fs = yield* FileSystem.FileSystem;
17 const configDirectory = join(workspaceRoot, ".executor");33 const configDirectory = join(scopeRoot, ".executor");
18 yield* fs.makeDirectory(configDirectory, { recursive: true });34 yield* fs.makeDirectory(configDirectory, { recursive: true });
19 yield* fs.writeFileString(35 yield* fs.writeFileString(
20 join(configDirectory, "executor.jsonc"),36 join(configDirectory, "executor.jsonc"),
@@ -26,16 +42,16 @@ const makeRuntime = (config: Record<string, unknown>) => { · 5 unmodified lines
26 return Effect.acquireRelease(42 return Effect.acquireRelease(
27 Effect.gen(function* () {43 Effect.gen(function* () {
28 const fs = yield* FileSystem.FileSystem;44 const fs = yield* FileSystem.FileSystem;
29 const workspaceRoot = yield* fs.makeTempDirectory({45 const scopeRoot = yield* fs.makeTempDirectory({
30 directory: tmpdir(),46 directory: tmpdir(),
31 prefix: "executor-runtime-config-",47 prefix: "executor-runtime-config-",
32 });48 });
33 yield* writeProjectConfig(workspaceRoot, config);49 yield* writeProjectConfig(scopeRoot, config);
34 50
35 return yield* createExecutorRuntime({51 return yield* createExecutorRuntime({
36 workspaceRoot,52 workspaceRoot: scopeRoot,
37 homeConfigPath: join(workspaceRoot, ".executor-home.jsonc"),53 homeConfigPath: join(scopeRoot, ".executor-home.jsonc"),
38 homeStateDirectory: join(workspaceRoot, ".executor-home-state"),54 homeStateDirectory: join(scopeRoot, ".executor-home-state"),
39 });55 });
40 }).pipe(Effect.provide(NodeFileSystem.layer)),56 }).pipe(Effect.provide(NodeFileSystem.layer)),
41 (runtime) => Effect.promise(() => runtime.close()).pipe(Effect.orDie),57 (runtime) => Effect.promise(() => runtime.close()).pipe(Effect.orDie),
@@ -53,12 +69,12 @@ describe("execution runtime config", () => { · 11 unmodified lines
53 const execution = yield* withExecutorApiClient(69 const execution = yield* withExecutorApiClient(
54 {70 {
55 runtime,71 runtime,
56 accountId: installation.accountId,72 actorScopeId: installation.actorScopeId,
57 },73 },
58 (client) =>74 (client) =>
59 client.executions.create({75 client.executions.create({
60 path: {76 path: {
61 workspaceId: installation.workspaceId,77 scopeId: installation.scopeId,
62 },78 },
63 payload: {79 payload: {
64 code: 'await fetch("https://example.com"); return 1;',80 code: 'await fetch("https://example.com"); return 1;',
@@ -85,12 +101,12 @@ describe("execution runtime config", () => { · 20 unmodified lines
85 const execution = yield* withExecutorApiClient(101 const execution = yield* withExecutorApiClient(
86 {102 {
87 runtime,103 runtime,
88 accountId: installation.accountId,104 actorScopeId: installation.actorScopeId,
89 },105 },
90 (client) =>106 (client) =>
91 client.executions.create({107 client.executions.create({
92 path: {108 path: {
93 workspaceId: installation.workspaceId,109 scopeId: installation.scopeId,
94 },110 },
95 payload: {111 payload: {
96 code: 'await fetch("https://example.com"); return 1;',112 code: 'await fetch("https://example.com"); return 1;',
@@ -117,12 +133,12 @@ describe("execution runtime config", () => { · 20 unmodified lines
117 const execution = yield* withExecutorApiClient(133 const execution = yield* withExecutorApiClient(
118 {134 {
119 runtime,135 runtime,
120 accountId: installation.accountId,136 actorScopeId: installation.actorScopeId,
121 },137 },
122 (client) =>138 (client) =>
123 client.executions.create({139 client.executions.create({
124 path: {140 path: {
125 workspaceId: installation.workspaceId,141 scopeId: installation.scopeId,
126 },142 },
127 payload: {143 payload: {
128 code: 'return typeof Deno !== "undefined";',144 code: 'return typeof Deno !== "undefined";',
packages/platform/sdk/src/runtime/execution/runtime.ts+16−5
@@ -1,8 +1,19 @@
1import type { CodeExecutor } from "@executor/codemode-core";1import type {
2import { makeDenoSubprocessExecutor } from "@executor/runtime-deno-subprocess";2 CodeExecutor,
3import { makeQuickJsExecutor } from "@executor/runtime-quickjs";3} from "@executor/codemode-core";
4import { makeSesExecutor } from "@executor/runtime-ses";4import {
5import type { LocalExecutorConfig, LocalExecutorRuntime } from "#schema";5 makeDenoSubprocessExecutor,
6} from "@executor/runtime-deno-subprocess";
7import {
8 makeQuickJsExecutor,
9} from "@executor/runtime-quickjs";
10import {
11 makeSesExecutor,
12} from "@executor/runtime-ses";
13import type {
14 LocalExecutorConfig,
15 LocalExecutorRuntime,
16} from "#schema";
6 17
7const DEFAULT_EXECUTION_RUNTIME: LocalExecutorRuntime = "quickjs";18const DEFAULT_EXECUTION_RUNTIME: LocalExecutorRuntime = "quickjs";
8 19
packages/platform/sdk/src/runtime/execution/scope/authorization.tsrenamed from packages/platform/sdk/src/runtime/execution/workspace/authorization.ts+33−16
@@ -1,15 +1,32 @@
1import { type ToolPath, makeToolInvokerFromTools } from "@executor/codemode-core";1import {
2import type { Source } from "#schema";2 type ToolPath,
3 makeToolInvokerFromTools,
4} from "@executor/codemode-core";
5import type {
6 Source,
7} from "#schema";
3import * as Either from "effect/Either";8import * as Either from "effect/Either";
4import * as Effect from "effect/Effect";9import * as Effect from "effect/Effect";
5import * as Schema from "effect/Schema";10import * as Schema from "effect/Schema";
6 11
7import type { LoadedSourceCatalogToolIndexEntry } from "../../catalog/source/runtime";12import type {
8import type { SecretMaterialResolveContext } from "../../workspace/secret-material-providers";13 LoadedSourceCatalogToolIndexEntry,
9import type { WorkspaceStorageServices } from "../../workspace/storage";14} from "../../catalog/source/runtime";
10import { invocationDescriptorFromTool } from "../ir-execution";15import type {
11import { evaluateInvocationPolicy } from "../../policy/invocation-policy-engine";16 SecretMaterialResolveContext,
12import { loadRuntimeLocalWorkspacePolicies } from "../../../policies/operations";17} from "../../scope/secret-material-providers";
18import type {
19 ScopeStorageServices,
20} from "../../scope/storage";
21import {
22 invocationDescriptorFromTool,
23} from "../ir-execution";
24import {
25 evaluateInvocationPolicy,
26} from "../../policy/invocation-policy-engine";
27import {
28 loadRuntimeLocalScopePolicies,
29} from "../../../policies/operations";
13 30
14const asToolPath = (value: string): ToolPath => value as ToolPath;31const asToolPath = (value: string): ToolPath => value as ToolPath;
15 32
@@ -64,23 +81,23 @@ export const toSecretResolutionContext = ( · 48 unmodified lines
64const failAuthorization = (message: string): Effect.Effect<never, Error, never> =>81const failAuthorization = (message: string): Effect.Effect<never, Error, never> =>
65 Effect.fail(new Error(message));82 Effect.fail(new Error(message));
66 83
67type WorkspaceToolElicitation = Parameters<84type ScopeToolElicitation = Parameters<
68 typeof makeToolInvokerFromTools85 typeof makeToolInvokerFromTools
69>[0]["onElicitation"];86>[0]["onElicitation"];
70 87
71export const authorizePersistedToolInvocation = (input: {88export const authorizePersistedToolInvocation = (input: {
72 workspaceId: Source["workspaceId"];89 scopeId: Source["scopeId"];
73 tool: LoadedSourceCatalogToolIndexEntry;90 tool: LoadedSourceCatalogToolIndexEntry;
74 args: unknown;91 args: unknown;
75 context?: Record<string, unknown>;92 context?: Record<string, unknown>;
76 onElicitation?: WorkspaceToolElicitation;93 onElicitation?: ScopeToolElicitation;
77}): Effect.Effect<void, Error, WorkspaceStorageServices> =>94}): Effect.Effect<void, Error, ScopeStorageServices> =>
78 Effect.gen(function* () {95 Effect.gen(function* () {
79 const descriptor = invocationDescriptorFromTool({96 const descriptor = invocationDescriptorFromTool({
80 tool: input.tool,97 tool: input.tool,
81 });98 });
82 const localWorkspacePolicies = yield* loadRuntimeLocalWorkspacePolicies(99 const localScopePolicies = yield* loadRuntimeLocalScopePolicies(
83 input.workspaceId,100 input.scopeId,
84 ).pipe(101 ).pipe(
85 Effect.mapError((cause) =>102 Effect.mapError((cause) =>
86 cause instanceof Error ? cause : new Error(String(cause)),103 cause instanceof Error ? cause : new Error(String(cause)),
@@ -90,9 +107,9 @@ export const authorizePersistedToolInvocation = (input: { · 3 unmodified lines
90 const decision = evaluateInvocationPolicy({107 const decision = evaluateInvocationPolicy({
91 descriptor,108 descriptor,
92 args: input.args,109 args: input.args,
93 policies: localWorkspacePolicies.policies,110 policies: localScopePolicies.policies,
94 context: {111 context: {
95 workspaceId: input.workspaceId,112 scopeId: input.scopeId,
96 },113 },
97 });114 });
98 115
packages/platform/sdk/src/runtime/execution/scope/environment.tsrenamed from packages/platform/sdk/src/runtime/execution/workspace/environment.ts+55−37
@@ -14,18 +14,30 @@ import { · 13 unmodified lines
14 createCodeExecutorForRuntime,14 createCodeExecutorForRuntime,
15 resolveConfiguredExecutionRuntime,15 resolveConfiguredExecutionRuntime,
16} from "../runtime";16} from "../runtime";
17import { createWorkspaceToolInvoker } from "./tool-invoker";17import {
18import { RuntimeSourceAuthServiceTag } from "../../sources/source-auth-service";18 createScopeToolInvoker,
19import { RuntimeSourceCatalogStoreService } from "../../catalog/source/runtime";19} from "./tool-invoker";
20import { RuntimeSourceAuthMaterialService } from "../../auth/source-auth-material";20import {
21import { RuntimeSourceCatalogSyncService } from "../../catalog/source/sync";21 RuntimeSourceAuthServiceTag,
22import { getRuntimeLocalWorkspaceOption } from "../../workspace/runtime-context";22} from "../../sources/source-auth-service";
23import {
24 RuntimeSourceCatalogStoreService,
25} from "../../catalog/source/runtime";
26import {
27 RuntimeSourceAuthMaterialService,
28} from "../../auth/source-auth-material";
29import {
30 RuntimeSourceCatalogSyncService,
31} from "../../catalog/source/sync";
32import {
33 getRuntimeLocalScopeOption,
34} from "../../scope/runtime-context";
23import {35import {
24 LocalInstanceConfigService,36 LocalInstanceConfigService,
25 SecretMaterialDeleterService,37 SecretMaterialDeleterService,
26 SecretMaterialStorerService,38 SecretMaterialStorerService,
27 SecretMaterialUpdaterService,39 SecretMaterialUpdaterService,
28} from "../../workspace/secret-material-providers";40} from "../../scope/secret-material-providers";
29import {41import {
30 LocalToolRuntimeLoaderService,42 LocalToolRuntimeLoaderService,
31 type LocalToolRuntimeLoaderShape,43 type LocalToolRuntimeLoaderShape,
@@ -36,14 +48,20 @@ import { · 4 unmodified lines
36 type InstallationStoreShape,48 type InstallationStoreShape,
37 SourceArtifactStore,49 SourceArtifactStore,
38 type SourceArtifactStoreShape,50 type SourceArtifactStoreShape,
39 WorkspaceConfigStore,51 ScopeConfigStore,
40 type WorkspaceConfigStoreShape,52 type ScopeConfigStoreShape,
41 WorkspaceStateStore,53 ScopeStateStore,
42 type WorkspaceStateStoreShape,54 type ScopeStateStoreShape,
43} from "../../workspace/storage";55} from "../../scope/storage";
44import { ExecutorStateStore } from "../../executor-state-store";56import {
45import { RuntimeSourceStoreService } from "../../sources/source-store";57 ExecutorStateStore,
46import type { CreateWorkspaceInternalToolMap } from "./tool-invoker";58} from "../../executor-state-store";
59import {
60 RuntimeSourceStoreService,
61} from "../../sources/source-store";
62import type {
63 CreateScopeInternalToolMap,
64} from "./tool-invoker";
47export {65export {
48 createCodeExecutorForRuntime,66 createCodeExecutorForRuntime,
49 resolveConfiguredExecutionRuntime,67 resolveConfiguredExecutionRuntime,
@@ -56,7 +74,7 @@ const createEmptyLocalToolRuntime = (): LocalToolRuntime => ({ · 6 unmodified lines
56 toolPaths: new Set<string>(),74 toolPaths: new Set<string>(),
57});75});
58 76
59export const createWorkspaceExecutionEnvironmentResolver =77export const createScopeExecutionEnvironmentResolver =
60 (input: {78 (input: {
61 executorStateStore: Effect.Effect.Success<typeof ExecutorStateStore>;79 executorStateStore: Effect.Effect.Success<typeof ExecutorStateStore>;
62 sourceStore: Effect.Effect.Success<typeof RuntimeSourceStoreService>;80 sourceStore: Effect.Effect.Success<typeof RuntimeSourceStoreService>;
@@ -78,25 +96,25 @@ export const createWorkspaceExecutionEnvironmentResolver = · 15 unmodified lines
78 storeSecretMaterial: Effect.Effect.Success<typeof SecretMaterialStorerService>;96 storeSecretMaterial: Effect.Effect.Success<typeof SecretMaterialStorerService>;
79 deleteSecretMaterial: Effect.Effect.Success<typeof SecretMaterialDeleterService>;97 deleteSecretMaterial: Effect.Effect.Success<typeof SecretMaterialDeleterService>;
80 updateSecretMaterial: Effect.Effect.Success<typeof SecretMaterialUpdaterService>;98 updateSecretMaterial: Effect.Effect.Success<typeof SecretMaterialUpdaterService>;
81 workspaceConfigStore: WorkspaceConfigStoreShape;99 scopeConfigStore: ScopeConfigStoreShape;
82 workspaceStateStore: WorkspaceStateStoreShape;100 scopeStateStore: ScopeStateStoreShape;
83 sourceArtifactStore: SourceArtifactStoreShape;101 sourceArtifactStore: SourceArtifactStoreShape;
84 createInternalToolMap?: CreateWorkspaceInternalToolMap;102 createInternalToolMap?: CreateScopeInternalToolMap;
85 }): ResolveExecutionEnvironment =>103 }): ResolveExecutionEnvironment =>
86 ({ workspaceId, accountId, onElicitation }) =>104 ({ scopeId, actorScopeId, onElicitation }) =>
87 Effect.gen(function* () {105 Effect.gen(function* () {
88 const runtimeLocalWorkspace = yield* getRuntimeLocalWorkspaceOption();106 const runtimeLocalScope = yield* getRuntimeLocalScopeOption();
89 const loadedConfig =107 const loadedConfig =
90 runtimeLocalWorkspace === null108 runtimeLocalScope === null
91 ? null109 ? null
92 : yield* input.workspaceConfigStore.load();110 : yield* input.scopeConfigStore.load();
93 const localToolRuntime =111 const localToolRuntime =
94 runtimeLocalWorkspace === null112 runtimeLocalScope === null
95 ? createEmptyLocalToolRuntime()113 ? createEmptyLocalToolRuntime()
96 : yield* input.localToolRuntimeLoader.load();114 : yield* input.localToolRuntimeLoader.load();
97 const { catalog, toolInvoker } = createWorkspaceToolInvoker({115 const { catalog, toolInvoker } = createScopeToolInvoker({
98 workspaceId,116 scopeId,
99 accountId,117 actorScopeId,
100 executorStateStore: input.executorStateStore,118 executorStateStore: input.executorStateStore,
101 sourceStore: input.sourceStore,119 sourceStore: input.sourceStore,
102 sourceCatalogSyncService: input.sourceCatalogSyncService,120 sourceCatalogSyncService: input.sourceCatalogSyncService,
@@ -106,12 +124,12 @@ export const createWorkspaceExecutionEnvironmentResolver = · 3 unmodified lines
106 storeSecretMaterial: input.storeSecretMaterial,124 storeSecretMaterial: input.storeSecretMaterial,
107 deleteSecretMaterial: input.deleteSecretMaterial,125 deleteSecretMaterial: input.deleteSecretMaterial,
108 updateSecretMaterial: input.updateSecretMaterial,126 updateSecretMaterial: input.updateSecretMaterial,
109 workspaceConfigStore: input.workspaceConfigStore,127 scopeConfigStore: input.scopeConfigStore,
110 workspaceStateStore: input.workspaceStateStore,128 scopeStateStore: input.scopeStateStore,
111 sourceArtifactStore: input.sourceArtifactStore,129 sourceArtifactStore: input.sourceArtifactStore,
112 sourceAuthMaterialService: input.sourceAuthMaterialService,130 sourceAuthMaterialService: input.sourceAuthMaterialService,
113 sourceAuthService: input.sourceAuthService,131 sourceAuthService: input.sourceAuthService,
114 runtimeLocalWorkspace,132 runtimeLocalScope,
115 localToolRuntime,133 localToolRuntime,
116 createInternalToolMap: input.createInternalToolMap,134 createInternalToolMap: input.createInternalToolMap,
117 onElicitation,135 onElicitation,
@@ -132,13 +150,13 @@ export class RuntimeExecutionResolverService extends Context.Tag( · 14 unmodified lines
132 "#runtime/RuntimeExecutionResolverService",150 "#runtime/RuntimeExecutionResolverService",
133)<151)<
134 RuntimeExecutionResolverService,152 RuntimeExecutionResolverService,
135 ReturnType<typeof createWorkspaceExecutionEnvironmentResolver>153 ReturnType<typeof createScopeExecutionEnvironmentResolver>
136>() {}154>() {}
137 155
138export const RuntimeExecutionResolverLive = (156export const RuntimeExecutionResolverLive = (
139 input: {157 input: {
140 executionResolver?: ResolveExecutionEnvironment;158 executionResolver?: ResolveExecutionEnvironment;
141 createInternalToolMap?: CreateWorkspaceInternalToolMap;159 createInternalToolMap?: CreateScopeInternalToolMap;
142 } = {},160 } = {},
143) =>161) =>
144 input.executionResolver162 input.executionResolver
@@ -160,11 +178,11 @@ export const RuntimeExecutionResolverLive = ( · 15 unmodified lines
160 const storeSecretMaterial = yield* SecretMaterialStorerService;178 const storeSecretMaterial = yield* SecretMaterialStorerService;
161 const deleteSecretMaterial = yield* SecretMaterialDeleterService;179 const deleteSecretMaterial = yield* SecretMaterialDeleterService;
162 const updateSecretMaterial = yield* SecretMaterialUpdaterService;180 const updateSecretMaterial = yield* SecretMaterialUpdaterService;
163 const workspaceConfigStore = yield* WorkspaceConfigStore;181 const scopeConfigStore = yield* ScopeConfigStore;
164 const workspaceStateStore = yield* WorkspaceStateStore;182 const scopeStateStore = yield* ScopeStateStore;
165 const sourceArtifactStore = yield* SourceArtifactStore;183 const sourceArtifactStore = yield* SourceArtifactStore;
166 184
167 return createWorkspaceExecutionEnvironmentResolver({185 return createScopeExecutionEnvironmentResolver({
168 executorStateStore,186 executorStateStore,
169 sourceStore,187 sourceStore,
170 sourceCatalogSyncService,188 sourceCatalogSyncService,
@@ -177,8 +195,8 @@ export const RuntimeExecutionResolverLive = ( · 6 unmodified lines
177 storeSecretMaterial,195 storeSecretMaterial,
178 deleteSecretMaterial,196 deleteSecretMaterial,
179 updateSecretMaterial,197 updateSecretMaterial,
180 workspaceConfigStore,198 scopeConfigStore,
181 workspaceStateStore,199 scopeStateStore,
182 sourceArtifactStore,200 sourceArtifactStore,
183 createInternalToolMap: input.createInternalToolMap,201 createInternalToolMap: input.createInternalToolMap,
184 });202 });
packages/platform/sdk/src/runtime/execution/scope/local.ts+12−0
@@ -0,0 +1,12 @@
1import * as Effect from "effect/Effect";
2
3import {
4 provideOptionalRuntimeLocalScope,
5 type RuntimeLocalScopeState,
6} from "../../scope/runtime-context";
7
8export const provideRuntimeLocalScope = <A, E, R>(
9 effect: Effect.Effect<A, E, R>,
10 runtimeLocalScope: RuntimeLocalScopeState | null,
11): Effect.Effect<A, E, R> =>
12 provideOptionalRuntimeLocalScope(effect, runtimeLocalScope);
packages/platform/sdk/src/runtime/execution/scope/source-catalog.tsrenamed from packages/platform/sdk/src/runtime/execution/workspace/source-catalog.ts+45−38
@@ -2,7 +2,10 @@ import { · 1 unmodified line
2 createToolCatalogFromEntries,2 createToolCatalogFromEntries,
3 type ToolCatalog,3 type ToolCatalog,
4} from "@executor/codemode-core";4} from "@executor/codemode-core";
5import type { AccountId, Source } from "#schema";5import type {
6 ScopeId,
7 Source,
8} from "#schema";
6import * as Effect from "effect/Effect";9import * as Effect from "effect/Effect";
7 10
8import {11import {
@@ -10,15 +13,19 @@ import { · 1 unmodified line
10 type LoadedSourceCatalogToolIndexEntry,13 type LoadedSourceCatalogToolIndexEntry,
11 catalogToolCatalogEntry,14 catalogToolCatalogEntry,
12} from "../../catalog/source/runtime";15} from "../../catalog/source/runtime";
13import type { RuntimeLocalWorkspaceState } from "../../workspace/runtime-context";16import type {
17 RuntimeLocalScopeState,
18} from "../../scope/runtime-context";
14import {19import {
15 makeWorkspaceStorageLayer,20 makeScopeStorageLayer,
16 type SourceArtifactStoreShape,21 type SourceArtifactStoreShape,
17 type WorkspaceConfigStoreShape,22 type ScopeConfigStoreShape,
18 type WorkspaceStateStoreShape,23 type ScopeStateStoreShape,
19 type WorkspaceStorageServices,24 type ScopeStorageServices,
20} from "../../workspace/storage";25} from "../../scope/storage";
21import { provideRuntimeLocalWorkspace } from "./local";26import {
27 provideRuntimeLocalScope,
28} from "./local";
22 29
23const tokenize = (value: string): string[] =>30const tokenize = (value: string): string[] =>
24 value31 value
@@ -74,19 +81,19 @@ const queryTokenWeight = (token: string): number => · 49 unmodified lines
74 LOW_SIGNAL_QUERY_TOKENS.has(token) ? 0.25 : 1;81 LOW_SIGNAL_QUERY_TOKENS.has(token) ? 0.25 : 1;
75 82
76export const loadWorkspaceCatalogTools = (input: {83export const loadWorkspaceCatalogTools = (input: {
77 workspaceId: Source["workspaceId"];84 scopeId: Source["scopeId"];
78 accountId: AccountId;85 actorScopeId: ScopeId;
79 sourceCatalogStore: Effect.Effect.Success<typeof RuntimeSourceCatalogStoreService>;86 sourceCatalogStore: Effect.Effect.Success<typeof RuntimeSourceCatalogStoreService>;
80 includeSchemas: boolean;87 includeSchemas: boolean;
81}): Effect.Effect<88}): Effect.Effect<
82 readonly LoadedSourceCatalogToolIndexEntry[],89 readonly LoadedSourceCatalogToolIndexEntry[],
83 Error,90 Error,
84 WorkspaceStorageServices91 ScopeStorageServices
85> =>92> =>
86 Effect.map(93 Effect.map(
87 input.sourceCatalogStore.loadWorkspaceSourceCatalogToolIndex({94 input.sourceCatalogStore.loadWorkspaceSourceCatalogToolIndex({
88 workspaceId: input.workspaceId,95 scopeId: input.scopeId,
89 actorAccountId: input.accountId,96 actorScopeId: input.actorScopeId,
90 includeSchemas: input.includeSchemas,97 includeSchemas: input.includeSchemas,
91 }),98 }),
92 (tools) =>99 (tools) =>
@@ -96,20 +103,20 @@ export const loadWorkspaceCatalogTools = (input: { · 3 unmodified lines
96 );103 );
97 104
98export const loadWorkspaceCatalogToolByPath = (input: {105export const loadWorkspaceCatalogToolByPath = (input: {
99 workspaceId: Source["workspaceId"];106 scopeId: Source["scopeId"];
100 accountId: AccountId;107 actorScopeId: ScopeId;
101 sourceCatalogStore: Effect.Effect.Success<typeof RuntimeSourceCatalogStoreService>;108 sourceCatalogStore: Effect.Effect.Success<typeof RuntimeSourceCatalogStoreService>;
102 path: string;109 path: string;
103 includeSchemas: boolean;110 includeSchemas: boolean;
104}): Effect.Effect<111}): Effect.Effect<
105 LoadedSourceCatalogToolIndexEntry | null,112 LoadedSourceCatalogToolIndexEntry | null,
106 Error,113 Error,
107 WorkspaceStorageServices114 ScopeStorageServices
108> =>115> =>
109 input.sourceCatalogStore.loadWorkspaceSourceCatalogToolByPath({116 input.sourceCatalogStore.loadWorkspaceSourceCatalogToolByPath({
110 workspaceId: input.workspaceId,117 scopeId: input.scopeId,
111 path: input.path,118 path: input.path,
112 actorAccountId: input.accountId,119 actorScopeId: input.actorScopeId,
113 includeSchemas: input.includeSchemas,120 includeSchemas: input.includeSchemas,
114 }).pipe(121 }).pipe(
115 Effect.map((tool) =>122 Effect.map((tool) =>
@@ -245,28 +252,28 @@ const scoreCatalogTool = ( · 129 unmodified lines
245 return score;252 return score;
246};253};
247 254
248export const createWorkspaceSourceCatalog = (input: {255export const createScopeSourceCatalog = (input: {
249 workspaceId: Source["workspaceId"];256 scopeId: Source["scopeId"];
250 accountId: AccountId;257 actorScopeId: ScopeId;
251 sourceCatalogStore: Effect.Effect.Success<typeof RuntimeSourceCatalogStoreService>;258 sourceCatalogStore: Effect.Effect.Success<typeof RuntimeSourceCatalogStoreService>;
252 workspaceConfigStore: WorkspaceConfigStoreShape;259 scopeConfigStore: ScopeConfigStoreShape;
253 workspaceStateStore: WorkspaceStateStoreShape;260 scopeStateStore: ScopeStateStoreShape;
254 sourceArtifactStore: SourceArtifactStoreShape;261 sourceArtifactStore: SourceArtifactStoreShape;
255 runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null;262 runtimeLocalScope: RuntimeLocalScopeState | null;
256}): ToolCatalog => {263}): ToolCatalog => {
257 const workspaceStorageLayer = makeWorkspaceStorageLayer({264 const scopeStorageLayer = makeScopeStorageLayer({
258 workspaceConfigStore: input.workspaceConfigStore,265 scopeConfigStore: input.scopeConfigStore,
259 workspaceStateStore: input.workspaceStateStore,266 scopeStateStore: input.scopeStateStore,
260 sourceArtifactStore: input.sourceArtifactStore,267 sourceArtifactStore: input.sourceArtifactStore,
261 });268 });
262 const provideWorkspaceStorage = <A, E, R>(effect: Effect.Effect<A, E, R>) =>269 const provideWorkspaceStorage = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
263 effect.pipe(Effect.provide(workspaceStorageLayer));270 effect.pipe(Effect.provide(scopeStorageLayer));
264 271
265 const createSharedCatalog = (includeSchemas: boolean): Effect.Effect<ToolCatalog, Error, never> =>272 const createSharedCatalog = (includeSchemas: boolean): Effect.Effect<ToolCatalog, Error, never> =>
266 provideWorkspaceStorage(Effect.gen(function* () {273 provideWorkspaceStorage(Effect.gen(function* () {
267 const catalogTools = yield* loadWorkspaceCatalogTools({274 const catalogTools = yield* loadWorkspaceCatalogTools({
268 workspaceId: input.workspaceId,275 scopeId: input.scopeId,
269 accountId: input.accountId,276 actorScopeId: input.actorScopeId,
270 sourceCatalogStore: input.sourceCatalogStore,277 sourceCatalogStore: input.sourceCatalogStore,
271 includeSchemas,278 includeSchemas,
272 });279 });
@@ -283,15 +290,15 @@ export const createWorkspaceSourceCatalog = (input: { · 10 unmodified lines
283 290
284 return {291 return {
285 listNamespaces: ({ limit }) =>292 listNamespaces: ({ limit }) =>
286 provideRuntimeLocalWorkspace(293 provideRuntimeLocalScope(
287 Effect.flatMap(createSharedCatalog(false), (catalog) =>294 Effect.flatMap(createSharedCatalog(false), (catalog) =>
288 catalog.listNamespaces({ limit }),295 catalog.listNamespaces({ limit }),
289 ),296 ),
290 input.runtimeLocalWorkspace,297 input.runtimeLocalScope,
291 ),298 ),
292 299
293 listTools: ({ namespace, query, limit, includeSchemas = false }) =>300 listTools: ({ namespace, query, limit, includeSchemas = false }) =>
294 provideRuntimeLocalWorkspace(301 provideRuntimeLocalScope(
295 Effect.flatMap(createSharedCatalog(includeSchemas), (catalog) =>302 Effect.flatMap(createSharedCatalog(includeSchemas), (catalog) =>
296 catalog.listTools({303 catalog.listTools({
297 ...(namespace !== undefined ? { namespace } : {}),304 ...(namespace !== undefined ? { namespace } : {}),
@@ -300,19 +307,19 @@ export const createWorkspaceSourceCatalog = (input: { · 2 unmodified lines
300 includeSchemas,307 includeSchemas,
301 }),308 }),
302 ),309 ),
303 input.runtimeLocalWorkspace,310 input.runtimeLocalScope,
304 ),311 ),
305 312
306 getToolByPath: ({ path, includeSchemas }) =>313 getToolByPath: ({ path, includeSchemas }) =>
307 provideRuntimeLocalWorkspace(314 provideRuntimeLocalScope(
308 Effect.flatMap(createSharedCatalog(includeSchemas), (catalog) =>315 Effect.flatMap(createSharedCatalog(includeSchemas), (catalog) =>
309 catalog.getToolByPath({ path, includeSchemas }),316 catalog.getToolByPath({ path, includeSchemas }),
310 ),317 ),
311 input.runtimeLocalWorkspace,318 input.runtimeLocalScope,
312 ),319 ),
313 320
314 searchTools: ({ query, namespace, limit }) =>321 searchTools: ({ query, namespace, limit }) =>
315 provideRuntimeLocalWorkspace(322 provideRuntimeLocalScope(
316 Effect.flatMap(createSharedCatalog(false), (catalog) =>323 Effect.flatMap(createSharedCatalog(false), (catalog) =>
317 catalog.searchTools({324 catalog.searchTools({
318 query,325 query,
@@ -320,7 +327,7 @@ export const createWorkspaceSourceCatalog = (input: { · 1 unmodified line
320 limit,327 limit,
321 }),328 }),
322 ),329 ),
323 input.runtimeLocalWorkspace,330 input.runtimeLocalScope,
324 ),331 ),
325 } satisfies ToolCatalog;332 } satisfies ToolCatalog;
326};333};
packages/platform/sdk/src/runtime/execution/scope/tool-invoker.tsrenamed from packages/platform/sdk/src/runtime/execution/workspace/tool-invoker.ts+92−65
@@ -8,46 +8,73 @@ import { · 7 unmodified lines
8 type ToolMap,8 type ToolMap,
9 type ToolInvoker,9 type ToolInvoker,
10} from "@executor/codemode-core";10} from "@executor/codemode-core";
11import type { AccountId, Source } from "#schema";11import type {
12 ScopeId,
13 Source,
14} from "#schema";
12import * as Effect from "effect/Effect";15import * as Effect from "effect/Effect";
13 16
14import { RuntimeSourceAuthMaterialService } from "../../auth/source-auth-material";17import {
15import { RuntimeSourceCatalogStoreService } from "../../catalog/source/runtime";18 RuntimeSourceAuthMaterialService,
16import type { RuntimeLocalWorkspaceState } from "../../workspace/runtime-context";19} from "../../auth/source-auth-material";
17import { type LocalToolRuntime } from "../../local-tool-runtime";20import {
21 RuntimeSourceCatalogStoreService,
22} from "../../catalog/source/runtime";
23import type {
24 RuntimeLocalScopeState,
25} from "../../scope/runtime-context";
26import {
27 type LocalToolRuntime,
28} from "../../local-tool-runtime";
18import {29import {
19 type InstallationStoreShape,30 type InstallationStoreShape,
20 makeWorkspaceStorageLayer,31 makeScopeStorageLayer,
21 type SourceArtifactStoreShape,32 type SourceArtifactStoreShape,
22 type WorkspaceConfigStoreShape,33 type ScopeConfigStoreShape,
23 type WorkspaceStateStoreShape,34 type ScopeStateStoreShape,
24} from "../../workspace/storage";35} from "../../scope/storage";
25import type {36import type {
26 DeleteSecretMaterial,37 DeleteSecretMaterial,
27 ResolveInstanceConfig,38 ResolveInstanceConfig,
28 StoreSecretMaterial,39 StoreSecretMaterial,
29 UpdateSecretMaterial,40 UpdateSecretMaterial,
30} from "../../workspace/secret-material-providers";41} from "../../scope/secret-material-providers";
31import type { ExecutorStateStoreShape } from "../../executor-state-store";42import type {
32import { RuntimeSourceAuthService } from "../../sources/source-auth-service";43 ExecutorStateStoreShape,
33import { type RuntimeSourceStore } from "../../sources/source-store";44} from "../../executor-state-store";
34import { createExecutorToolMap } from "../../sources/executor-tools";45import {
35import { RuntimeSourceCatalogSyncService } from "../../catalog/source/sync";46 RuntimeSourceAuthService,
36import { invokeIrTool } from "../ir-execution";47} from "../../sources/source-auth-service";
48import {
49 type RuntimeSourceStore,
50} from "../../sources/source-store";
51import {
52 createExecutorToolMap,
53} from "../../sources/executor-tools";
54import {
55 RuntimeSourceCatalogSyncService,
56} from "../../catalog/source/sync";
57import {
58 invokeIrTool,
59} from "../ir-execution";
37import {60import {
38 authorizePersistedToolInvocation,61 authorizePersistedToolInvocation,
39 toSecretResolutionContext,62 toSecretResolutionContext,
40} from "./authorization";63} from "./authorization";
41import { provideRuntimeLocalWorkspace } from "./local";
42import {64import {
43 createWorkspaceSourceCatalog,65 provideRuntimeLocalScope,
66} from "./local";
67import {
68 createScopeSourceCatalog,
44 loadWorkspaceCatalogToolByPath,69 loadWorkspaceCatalogToolByPath,
45} from "./source-catalog";70} from "./source-catalog";
46import { runtimeEffectError } from "../../effect-errors";71import {
72 runtimeEffectError,
73} from "../../effect-errors";
47 74
48export type WorkspaceInternalToolContext = {75export type ScopeInternalToolContext = {
49 workspaceId: Source["workspaceId"];76 scopeId: Source["scopeId"];
50 accountId: AccountId;77 actorScopeId: ScopeId;
51 executorStateStore: ExecutorStateStoreShape;78 executorStateStore: ExecutorStateStoreShape;
52 sourceStore: RuntimeSourceStore;79 sourceStore: RuntimeSourceStore;
53 sourceCatalogSyncService: Effect.Effect.Success<80 sourceCatalogSyncService: Effect.Effect.Success<
@@ -59,19 +86,19 @@ export type WorkspaceInternalToolContext = { · 5 unmodified lines
59 storeSecretMaterial: StoreSecretMaterial;86 storeSecretMaterial: StoreSecretMaterial;
60 deleteSecretMaterial: DeleteSecretMaterial;87 deleteSecretMaterial: DeleteSecretMaterial;
61 updateSecretMaterial: UpdateSecretMaterial;88 updateSecretMaterial: UpdateSecretMaterial;
62 workspaceConfigStore: WorkspaceConfigStoreShape;89 scopeConfigStore: ScopeConfigStoreShape;
63 workspaceStateStore: WorkspaceStateStoreShape;90 scopeStateStore: ScopeStateStoreShape;
64 sourceArtifactStore: SourceArtifactStoreShape;91 sourceArtifactStore: SourceArtifactStoreShape;
65 runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null;92 runtimeLocalScope: RuntimeLocalScopeState | null;
66};93};
67 94
68export type CreateWorkspaceInternalToolMap = (95export type CreateScopeInternalToolMap = (
69 input: WorkspaceInternalToolContext,96 input: ScopeInternalToolContext,
70) => ToolMap;97) => ToolMap;
71 98
72export const createWorkspaceToolInvoker = (input: {99export const createScopeToolInvoker = (input: {
73 workspaceId: Source["workspaceId"];100 scopeId: Source["scopeId"];
74 accountId: AccountId;101 actorScopeId: ScopeId;
75 executorStateStore: ExecutorStateStoreShape;102 executorStateStore: ExecutorStateStoreShape;
76 sourceStore: RuntimeSourceStore;103 sourceStore: RuntimeSourceStore;
77 sourceCatalogSyncService: Effect.Effect.Success<104 sourceCatalogSyncService: Effect.Effect.Success<
@@ -85,16 +112,16 @@ export const createWorkspaceToolInvoker = (input: { · 7 unmodified lines
85 storeSecretMaterial: StoreSecretMaterial;112 storeSecretMaterial: StoreSecretMaterial;
86 deleteSecretMaterial: DeleteSecretMaterial;113 deleteSecretMaterial: DeleteSecretMaterial;
87 updateSecretMaterial: UpdateSecretMaterial;114 updateSecretMaterial: UpdateSecretMaterial;
88 workspaceConfigStore: WorkspaceConfigStoreShape;115 scopeConfigStore: ScopeConfigStoreShape;
89 workspaceStateStore: WorkspaceStateStoreShape;116 scopeStateStore: ScopeStateStoreShape;
90 sourceArtifactStore: SourceArtifactStoreShape;117 sourceArtifactStore: SourceArtifactStoreShape;
91 sourceAuthMaterialService: Effect.Effect.Success<118 sourceAuthMaterialService: Effect.Effect.Success<
92 typeof RuntimeSourceAuthMaterialService119 typeof RuntimeSourceAuthMaterialService
93 >;120 >;
94 sourceAuthService: RuntimeSourceAuthService;121 sourceAuthService: RuntimeSourceAuthService;
95 runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null;122 runtimeLocalScope: RuntimeLocalScopeState | null;
96 localToolRuntime: LocalToolRuntime;123 localToolRuntime: LocalToolRuntime;
97 createInternalToolMap?: CreateWorkspaceInternalToolMap;124 createInternalToolMap?: CreateScopeInternalToolMap;
98 onElicitation?: Parameters<125 onElicitation?: Parameters<
99 typeof makeToolInvokerFromTools126 typeof makeToolInvokerFromTools
100 >[0]["onElicitation"];127 >[0]["onElicitation"];
@@ -102,28 +129,28 @@ export const createWorkspaceToolInvoker = (input: { · 1 unmodified line
102 catalog: ToolCatalog;129 catalog: ToolCatalog;
103 toolInvoker: ToolInvoker;130 toolInvoker: ToolInvoker;
104} => {131} => {
105 const workspaceStorageLayer = makeWorkspaceStorageLayer({132 const scopeStorageLayer = makeScopeStorageLayer({
106 workspaceConfigStore: input.workspaceConfigStore,133 scopeConfigStore: input.scopeConfigStore,
107 workspaceStateStore: input.workspaceStateStore,134 scopeStateStore: input.scopeStateStore,
108 sourceArtifactStore: input.sourceArtifactStore,135 sourceArtifactStore: input.sourceArtifactStore,
109 });136 });
110 const provideWorkspaceStorage = <A, E, R>(effect: Effect.Effect<A, E, R>) =>137 const provideWorkspaceStorage = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
111 effect.pipe(Effect.provide(workspaceStorageLayer));138 effect.pipe(Effect.provide(scopeStorageLayer));
112 139
113 const executorTools = createExecutorToolMap({140 const executorTools = createExecutorToolMap({
114 workspaceId: input.workspaceId,141 scopeId: input.scopeId,
115 accountId: input.accountId,142 actorScopeId: input.actorScopeId,
116 sourceAuthService: input.sourceAuthService,143 sourceAuthService: input.sourceAuthService,
117 installationStore: input.installationStore,144 installationStore: input.installationStore,
118 workspaceConfigStore: input.workspaceConfigStore,145 scopeConfigStore: input.scopeConfigStore,
119 workspaceStateStore: input.workspaceStateStore,146 scopeStateStore: input.scopeStateStore,
120 sourceArtifactStore: input.sourceArtifactStore,147 sourceArtifactStore: input.sourceArtifactStore,
121 runtimeLocalWorkspace: input.runtimeLocalWorkspace,148 runtimeLocalScope: input.runtimeLocalScope,
122 });149 });
123 const internalTools =150 const internalTools =
124 input.createInternalToolMap?.({151 input.createInternalToolMap?.({
125 workspaceId: input.workspaceId,152 scopeId: input.scopeId,
126 accountId: input.accountId,153 actorScopeId: input.actorScopeId,
127 executorStateStore: input.executorStateStore,154 executorStateStore: input.executorStateStore,
128 sourceStore: input.sourceStore,155 sourceStore: input.sourceStore,
129 sourceCatalogSyncService: input.sourceCatalogSyncService,156 sourceCatalogSyncService: input.sourceCatalogSyncService,
@@ -133,19 +160,19 @@ export const createWorkspaceToolInvoker = (input: { · 3 unmodified lines
133 storeSecretMaterial: input.storeSecretMaterial,160 storeSecretMaterial: input.storeSecretMaterial,
134 deleteSecretMaterial: input.deleteSecretMaterial,161 deleteSecretMaterial: input.deleteSecretMaterial,
135 updateSecretMaterial: input.updateSecretMaterial,162 updateSecretMaterial: input.updateSecretMaterial,
136 workspaceConfigStore: input.workspaceConfigStore,163 scopeConfigStore: input.scopeConfigStore,
137 workspaceStateStore: input.workspaceStateStore,164 scopeStateStore: input.scopeStateStore,
138 sourceArtifactStore: input.sourceArtifactStore,165 sourceArtifactStore: input.sourceArtifactStore,
139 runtimeLocalWorkspace: input.runtimeLocalWorkspace,166 runtimeLocalScope: input.runtimeLocalScope,
140 }) ?? {};167 }) ?? {};
141 const sourceCatalog = createWorkspaceSourceCatalog({168 const sourceCatalog = createScopeSourceCatalog({
142 workspaceId: input.workspaceId,169 scopeId: input.scopeId,
143 accountId: input.accountId,170 actorScopeId: input.actorScopeId,
144 sourceCatalogStore: input.sourceCatalogStore,171 sourceCatalogStore: input.sourceCatalogStore,
145 workspaceConfigStore: input.workspaceConfigStore,172 scopeConfigStore: input.scopeConfigStore,
146 workspaceStateStore: input.workspaceStateStore,173 scopeStateStore: input.scopeStateStore,
147 sourceArtifactStore: input.sourceArtifactStore,174 sourceArtifactStore: input.sourceArtifactStore,
148 runtimeLocalWorkspace: input.runtimeLocalWorkspace,175 runtimeLocalScope: input.runtimeLocalScope,
149 });176 });
150 let catalog: ToolCatalog | null = null;177 let catalog: ToolCatalog | null = null;
151 const systemTools = createSystemToolMap({178 const systemTools = createSystemToolMap({
@@ -180,25 +207,25 @@ export const createWorkspaceToolInvoker = (input: { · 28 unmodified lines
180 args: unknown;207 args: unknown;
181 context?: Record<string, unknown>;208 context?: Record<string, unknown>;
182 }) =>209 }) =>
183 provideRuntimeLocalWorkspace(210 provideRuntimeLocalScope(
184 provideWorkspaceStorage(211 provideWorkspaceStorage(
185 Effect.gen(function* () {212 Effect.gen(function* () {
186 const catalogTool = yield* loadWorkspaceCatalogToolByPath({213 const catalogTool = yield* loadWorkspaceCatalogToolByPath({
187 workspaceId: input.workspaceId,214 scopeId: input.scopeId,
188 accountId: input.accountId,215 actorScopeId: input.actorScopeId,
189 sourceCatalogStore: input.sourceCatalogStore,216 sourceCatalogStore: input.sourceCatalogStore,
190 path: invocation.path,217 path: invocation.path,
191 includeSchemas: false,218 includeSchemas: false,
192 });219 });
193 if (!catalogTool) {220 if (!catalogTool) {
194 return yield* runtimeEffectError(221 return yield* runtimeEffectError(
195 "execution/workspace/tool-invoker",222 "execution/scope/tool-invoker",
196 `Unknown tool path: ${invocation.path}`,223 `Unknown tool path: ${invocation.path}`,
197 );224 );
198 }225 }
199 226
200 yield* authorizePersistedToolInvocation({227 yield* authorizePersistedToolInvocation({
201 workspaceId: input.workspaceId,228 scopeId: input.scopeId,
202 tool: catalogTool,229 tool: catalogTool,
203 args: invocation.args,230 args: invocation.args,
204 context: invocation.context,231 context: invocation.context,
@@ -207,12 +234,12 @@ export const createWorkspaceToolInvoker = (input: { · 2 unmodified lines
207 234
208 const auth = yield* input.sourceAuthMaterialService.resolve({235 const auth = yield* input.sourceAuthMaterialService.resolve({
209 source: catalogTool.source,236 source: catalogTool.source,
210 actorAccountId: input.accountId,237 actorScopeId: input.actorScopeId,
211 context: toSecretResolutionContext(invocation.context),238 context: toSecretResolutionContext(invocation.context),
212 });239 });
213 return yield* invokeIrTool({240 return yield* invokeIrTool({
214 workspaceId: input.workspaceId,241 scopeId: input.scopeId,
215 accountId: input.accountId,242 actorScopeId: input.actorScopeId,
216 tool: catalogTool,243 tool: catalogTool,
217 auth,244 auth,
218 args: invocation.args,245 args: invocation.args,
@@ -221,18 +248,18 @@ export const createWorkspaceToolInvoker = (input: { · 2 unmodified lines
221 });248 });
222 }),249 }),
223 ),250 ),
224 input.runtimeLocalWorkspace,251 input.runtimeLocalScope,
225 );252 );
226 253
227 return {254 return {
228 catalog,255 catalog,
229 toolInvoker: {256 toolInvoker: {
230 invoke: ({ path, args, context }) =>257 invoke: ({ path, args, context }) =>
231 provideRuntimeLocalWorkspace(258 provideRuntimeLocalScope(
232 authoredToolPaths.has(path)259 authoredToolPaths.has(path)
233 ? authoredInvoker.invoke({ path, args, context })260 ? authoredInvoker.invoke({ path, args, context })
234 : invokePersistedTool({ path, args, context }),261 : invokePersistedTool({ path, args, context }),
235 input.runtimeLocalWorkspace,262 input.runtimeLocalScope,
236 ),263 ),
237 },264 },
238 };265 };
packages/platform/sdk/src/runtime/execution/service.ts+35−30
@@ -16,12 +16,11 @@ import { · 15 unmodified lines
16 ExecutionIdSchema,16 ExecutionIdSchema,
17 ExecutionInteractionIdSchema,17 ExecutionInteractionIdSchema,
18 ExecutionStepIdSchema,18 ExecutionStepIdSchema,
19 type AccountId,19 type ScopeId,
20 type Execution,20 type Execution,
21 type ExecutionEnvelope,21 type ExecutionEnvelope,
22 type ExecutionId,22 type ExecutionId,
23 type ExecutionInteraction,23 type ExecutionInteraction,
24 type WorkspaceId,
25} from "#schema";24} from "#schema";
26import * as Data from "effect/Data";25import * as Data from "effect/Data";
27import * as Deferred from "effect/Deferred";26import * as Deferred from "effect/Deferred";
@@ -29,16 +28,18 @@ import * as Effect from "effect/Effect"; · 1 unmodified line
29import * as Option from "effect/Option";28import * as Option from "effect/Option";
30import * as Schema from "effect/Schema";29import * as Schema from "effect/Schema";
31 30
32import { type ResolveExecutionEnvironment } from "./state";31import {
32 type ResolveExecutionEnvironment,
33} from "./state";
33import {34import {
34 LiveExecutionManagerService,35 LiveExecutionManagerService,
35 sanitizePersistedElicitationResponse,36 sanitizePersistedElicitationResponse,
36 type LiveExecutionManager,37 type LiveExecutionManager,
37} from "./live";38} from "./live";
38import {39import {
39 getRuntimeLocalWorkspaceOption,40 getRuntimeLocalScopeOption,
40 provideOptionalRuntimeLocalWorkspace,41 provideOptionalRuntimeLocalScope,
41} from "../workspace/runtime-context";42} from "../scope/runtime-context";
42import {43import {
43 asOperationErrors,44 asOperationErrors,
44 operationErrors,45 operationErrors,
@@ -48,8 +49,12 @@ import { · 3 unmodified lines
48 ExecutorStateStore,49 ExecutorStateStore,
49 type ExecutorStateStoreShape,50 type ExecutorStateStoreShape,
50} from "../executor-state-store";51} from "../executor-state-store";
51import { RuntimeExecutionResolverService } from "./workspace/environment";52import {
52import { runtimeEffectError } from "../effect-errors";53 RuntimeExecutionResolverService,
54} from "./scope/environment";
55import {
56 runtimeEffectError,
57} from "../effect-errors";
53 58
54const executionOps = {59const executionOps = {
55 create: operationErrors("executions.create"),60 create: operationErrors("executions.create"),
@@ -246,7 +251,7 @@ const verifyStoredStepMatches = (input: { · 190 unmodified lines
246const fetchExecution = (251const fetchExecution = (
247 store: ExecutorStateStoreShape,252 store: ExecutorStateStoreShape,
248 input: {253 input: {
249 workspaceId: Execution["workspaceId"];254 scopeId: Execution["scopeId"];
250 executionId: Execution["id"];255 executionId: Execution["id"];
251 operation: OperationErrorsLike;256 operation: OperationErrorsLike;
252 },257 },
@@ -254,13 +259,13 @@ const fetchExecution = ( · 1 unmodified line
254 Effect.gen(function* () {259 Effect.gen(function* () {
255 const errors = asOperationErrors(input.operation);260 const errors = asOperationErrors(input.operation);
256 const existing = yield* errors.mapStorage(261 const existing = yield* errors.mapStorage(
257 store.executions.getByWorkspaceAndId(input.workspaceId, input.executionId),262 store.executions.getByScopeAndId(input.scopeId, input.executionId),
258 );263 );
259 264
260 if (Option.isNone(existing)) {265 if (Option.isNone(existing)) {
261 return yield* errors.notFound(266 return yield* errors.notFound(
262 "Execution not found",267 "Execution not found",
263 `workspaceId=${input.workspaceId} executionId=${input.executionId}`,268 `scopeId=${input.scopeId} executionId=${input.executionId}`,
264 );269 );
265 }270 }
266 271
@@ -270,7 +275,7 @@ const fetchExecution = ( · 3 unmodified lines
270const fetchExecutionEnvelope = (275const fetchExecutionEnvelope = (
271 store: ExecutorStateStoreShape,276 store: ExecutorStateStoreShape,
272 input: {277 input: {
273 workspaceId: Execution["workspaceId"];278 scopeId: Execution["scopeId"];
274 executionId: Execution["id"];279 executionId: Execution["id"];
275 operation: OperationErrorsLike;280 operation: OperationErrorsLike;
276 },281 },
@@ -291,7 +296,7 @@ const fetchExecutionEnvelope = ( · 14 unmodified lines
291const waitForExecutionEnvelopeToSettle = (296const waitForExecutionEnvelopeToSettle = (
292 store: ExecutorStateStoreShape,297 store: ExecutorStateStoreShape,
293 input: {298 input: {
294 workspaceId: Execution["workspaceId"];299 scopeId: Execution["scopeId"];
295 executionId: Execution["id"];300 executionId: Execution["id"];
296 operation: OperationErrorsLike;301 operation: OperationErrorsLike;
297 previousPendingInteractionId: ExecutionInteraction["id"] | null;302 previousPendingInteractionId: ExecutionInteraction["id"] | null;
@@ -658,8 +663,8 @@ const runExecutionAttemptWithDependencies = ( · 360 unmodified lines
658 interactionMode: InteractionMode,663 interactionMode: InteractionMode,
659) =>664) =>
660 executionResolver({665 executionResolver({
661 workspaceId: execution.workspaceId,666 scopeId: execution.scopeId,
662 accountId: execution.createdByAccountId,667 actorScopeId: execution.createdByScopeId,
663 executionId: execution.id,668 executionId: execution.id,
664 onElicitation: createHybridOnElicitation({669 onElicitation: createHybridOnElicitation({
665 executorState: store,670 executorState: store,
@@ -710,7 +715,7 @@ const forkExecutionAttemptWithDependencies = ( · 44 unmodified lines
710 interactionMode: InteractionMode,715 interactionMode: InteractionMode,
711) =>716) =>
712 Effect.gen(function* () {717 Effect.gen(function* () {
713 const runtimeLocalWorkspace = yield* getRuntimeLocalWorkspaceOption();718 const runtimeLocalScope = yield* getRuntimeLocalScopeOption();
714 719
715 yield* Effect.sync(() => {720 yield* Effect.sync(() => {
716 const effect = runExecutionAttemptWithDependencies(721 const effect = runExecutionAttemptWithDependencies(
@@ -722,7 +727,7 @@ const forkExecutionAttemptWithDependencies = ( · 5 unmodified lines
722 );727 );
723 728
724 Effect.runFork(729 Effect.runFork(
725 provideOptionalRuntimeLocalWorkspace(effect, runtimeLocalWorkspace),730 provideOptionalRuntimeLocalScope(effect, runtimeLocalScope),
726 );731 );
727 });732 });
728 });733 });
@@ -808,9 +813,9 @@ const createExecutionWithDependencies = ( · 79 unmodified lines
808 executionResolver: ResolveExecutionEnvironment,813 executionResolver: ResolveExecutionEnvironment,
809 liveExecutionManager: LiveExecutionManager,814 liveExecutionManager: LiveExecutionManager,
810 input: {815 input: {
811 workspaceId: WorkspaceId;816 scopeId: ScopeId;
812 payload: CreateExecutionPayload;817 payload: CreateExecutionPayload;
813 createdByAccountId: AccountId;818 createdByScopeId: ScopeId;
814 },819 },
815) =>820) =>
816 Effect.gen(function* () {821 Effect.gen(function* () {
@@ -818,8 +823,8 @@ const createExecutionWithDependencies = ( · 1 unmodified line
818 const now = Date.now();823 const now = Date.now();
819 const execution: Execution = {824 const execution: Execution = {
820 id: ExecutionIdSchema.make(`exec_${crypto.randomUUID()}`),825 id: ExecutionIdSchema.make(`exec_${crypto.randomUUID()}`),
821 workspaceId: input.workspaceId,826 scopeId: input.scopeId,
822 createdByAccountId: input.createdByAccountId,827 createdByScopeId: input.createdByScopeId,
823 status: "pending",828 status: "pending",
824 code,829 code,
825 resultJson: null,830 resultJson: null,
@@ -863,16 +868,16 @@ const createExecutionWithDependencies = ( · 37 unmodified lines
863 yield* Deferred.await(nextState);868 yield* Deferred.await(nextState);
864 869
865 return yield* fetchExecutionEnvelope(store, {870 return yield* fetchExecutionEnvelope(store, {
866 workspaceId: input.workspaceId,871 scopeId: input.scopeId,
867 executionId: execution.id,872 executionId: execution.id,
868 operation: executionOps.create,873 operation: executionOps.create,
869 });874 });
870 });875 });
871 876
872export const createExecution = (input: {877export const createExecution = (input: {
873 workspaceId: WorkspaceId;878 scopeId: ScopeId;
874 payload: CreateExecutionPayload;879 payload: CreateExecutionPayload;
875 createdByAccountId: AccountId;880 createdByScopeId: ScopeId;
876}) =>881}) =>
877 Effect.gen(function* () {882 Effect.gen(function* () {
878 const store = yield* ExecutorStateStore;883 const store = yield* ExecutorStateStore;
@@ -888,12 +893,12 @@ export const createExecution = (input: { · 9 unmodified lines
888 });893 });
889 894
890export const getExecution = (input: {895export const getExecution = (input: {
891 workspaceId: WorkspaceId;896 scopeId: ScopeId;
892 executionId: ExecutionId;897 executionId: ExecutionId;
893}) =>898}) =>
894 Effect.flatMap(ExecutorStateStore, (store) =>899 Effect.flatMap(ExecutorStateStore, (store) =>
895 fetchExecutionEnvelope(store, {900 fetchExecutionEnvelope(store, {
896 workspaceId: input.workspaceId,901 scopeId: input.scopeId,
897 executionId: input.executionId,902 executionId: input.executionId,
898 operation: executionOps.get,903 operation: executionOps.get,
899 })904 })
@@ -921,10 +926,10 @@ export const submitExecutionInteractionResponse = (input: { · 21 unmodified lines
921 });926 });
922 927
923export const resumeExecution = (input: {928export const resumeExecution = (input: {
924 workspaceId: WorkspaceId;929 scopeId: ScopeId;
925 executionId: ExecutionId;930 executionId: ExecutionId;
926 payload: ResumeExecutionPayload;931 payload: ResumeExecutionPayload;
927 resumedByAccountId: AccountId;932 resumedByScopeId: ScopeId;
928}) =>933}) =>
929 Effect.gen(function* () {934 Effect.gen(function* () {
930 const store = yield* ExecutorStateStore;935 const store = yield* ExecutorStateStore;
@@ -932,7 +937,7 @@ export const resumeExecution = (input: { · 1 unmodified line
932 const liveExecutionManager = yield* LiveExecutionManagerService;937 const liveExecutionManager = yield* LiveExecutionManagerService;
933 938
934 const existing = yield* fetchExecutionEnvelope(store, {939 const existing = yield* fetchExecutionEnvelope(store, {
935 workspaceId: input.workspaceId,940 scopeId: input.scopeId,
936 executionId: input.executionId,941 executionId: input.executionId,
937 operation: "executions.resume",942 operation: "executions.resume",
938 });943 });
@@ -1002,7 +1007,7 @@ export const resumeExecution = (input: { · 63 unmodified lines
1002 }1007 }
1003 1008
1004 return yield* waitForExecutionEnvelopeToSettle(store, {1009 return yield* waitForExecutionEnvelopeToSettle(store, {
1005 workspaceId: input.workspaceId,1010 scopeId: input.scopeId,
1006 executionId: input.executionId,1011 executionId: input.executionId,
1007 operation: executionOps.resume,1012 operation: executionOps.resume,
1008 previousPendingInteractionId: existing.pendingInteraction?.id ?? null,1013 previousPendingInteractionId: existing.pendingInteraction?.id ?? null,
packages/platform/sdk/src/runtime/execution/state.ts+6−3
@@ -4,7 +4,10 @@ import type { · 3 unmodified lines
4 ToolCatalog,4 ToolCatalog,
5 ToolInvoker,5 ToolInvoker,
6} from "@executor/codemode-core";6} from "@executor/codemode-core";
7import type { AccountId, ExecutionId, WorkspaceId } from "#schema";7import type {
8 ScopeId,
9 ExecutionId,
10} from "#schema";
8import * as Data from "effect/Data";11import * as Data from "effect/Data";
9import type * as Effect from "effect/Effect";12import type * as Effect from "effect/Effect";
10 13
@@ -15,8 +18,8 @@ export type ExecutionEnvironment = { · 4 unmodified lines
15};18};
16 19
17export type ResolveExecutionEnvironment = (input: {20export type ResolveExecutionEnvironment = (input: {
18 workspaceId: WorkspaceId;21 scopeId: ScopeId;
19 accountId: AccountId;22 actorScopeId: ScopeId;
20 executionId: ExecutionId;23 executionId: ExecutionId;
21 onElicitation?: OnElicitation;24 onElicitation?: OnElicitation;
22}) => Effect.Effect<ExecutionEnvironment, unknown>;25}) => Effect.Effect<ExecutionEnvironment, unknown>;
packages/platform/sdk/src/runtime/execution/test-http-client.ts+4−2
@@ -3,7 +3,9 @@ import { · 2 unmodified lines
3 HttpApiClient,3 HttpApiClient,
4 HttpServer,4 HttpServer,
5} from "@effect/platform";5} from "@effect/platform";
6import { NodeHttpServer } from "@effect/platform-node";6import {
7 NodeHttpServer,
8} from "@effect/platform-node";
7import {9import {
8 ExecutorApi,10 ExecutorApi,
9 createExecutorApiLayer,11 createExecutorApiLayer,
@@ -93,7 +95,7 @@ type ExecutorApiClient = Effect.Effect.Success< · 83 unmodified lines
93export const withExecutorApiClient = <A, E>(95export const withExecutorApiClient = <A, E>(
94 input: {96 input: {
95 runtime: ExecutorRuntime;97 runtime: ExecutorRuntime;
96 accountId?: string;98 actorScopeId?: string;
97 },99 },
98 f: (client: ExecutorApiClient) => Effect.Effect<A, E, never>,100 f: (client: ExecutorApiClient) => Effect.Effect<A, E, never>,
99): Effect.Effect<A, E, never> =>101): Effect.Effect<A, E, never> =>
packages/platform/sdk/src/runtime/execution/workspace/local.ts+0−12
@@ -1,12 +0,0 @@
1import * as Effect from "effect/Effect";
2
3import {
4 provideOptionalRuntimeLocalWorkspace,
5 type RuntimeLocalWorkspaceState,
6} from "../../workspace/runtime-context";
7
8export const provideRuntimeLocalWorkspace = <A, E, R>(
9 effect: Effect.Effect<A, E, R>,
10 runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null,
11): Effect.Effect<A, E, R> =>
12 provideOptionalRuntimeLocalWorkspace(effect, runtimeLocalWorkspace);
packages/platform/sdk/src/runtime/executor-runtime.test.ts+195−195
@@ -1,14 +1,35 @@
1import { createServer } from "node:http";1import {
2import { join } from "node:path";2 createServer,
3} from "node:http";
4import {
5 join,
6} from "node:path";
3 7
4import { FileSystem } from "@effect/platform";8import {
5import { NodeFileSystem } from "@effect/platform-node";9 FileSystem,
6import { describe, expect, it } from "@effect/vitest";10 HttpApi,
7import { assertTrue } from "@effect/vitest/utils";11 HttpApiEndpoint,
12 HttpApiGroup,
13 HttpApiSchema,
14 HttpApiBuilder,
15} from "@effect/platform";
16import {
17 NodeFileSystem,
18} from "@effect/platform-node";
19import {
20 describe,
21 expect,
22 it,
23} from "@effect/vitest";
24import {
25 assertTrue,
26} from "@effect/vitest/utils";
8import * as Either from "effect/Either";27import * as Either from "effect/Either";
9import * as Effect from "effect/Effect";28import * as Effect from "effect/Effect";
10import * as Fiber from "effect/Fiber";29import * as Fiber from "effect/Fiber";
30import * as Layer from "effect/Layer";
11import * as Option from "effect/Option";31import * as Option from "effect/Option";
32import * as Schema from "effect/Schema";
12 33
13import {34import {
14 ExecutionIdSchema,35 ExecutionIdSchema,
@@ -18,12 +39,23 @@ import { · 3 unmodified lines
18 SecretMaterialIdSchema,39 SecretMaterialIdSchema,
19 SourceAuthSessionIdSchema,40 SourceAuthSessionIdSchema,
20 SourceIdSchema,41 SourceIdSchema,
21 WorkspaceOauthClientIdSchema,42 ScopeOauthClientIdSchema,
22} from "#schema";43} from "#schema";
23import type { ToolPath } from "@executor/codemode-core";44import type {
24import { createCatalogImportMetadata } from "@executor/source-core";45 ToolPath,
25import { createGraphqlCatalogFragment } from "@executor/source-graphql";46} from "@executor/codemode-core";
26import { createWorkspaceExecutorAdminToolMap } from "../../../internal/src/index";47import {
48 makeOpenApiTestServer,
49} from "@executor/effect-test-utils";
50import {
51 createCatalogImportMetadata,
52} from "@executor/source-core";
53import {
54 createGraphqlCatalogFragment,
55} from "@executor/source-graphql";
56import {
57 createWorkspaceExecutorAdminToolMap,
58} from "../../../internal/src/index";
27 59
28import {60import {
29 type ExecutorRuntime,61 type ExecutorRuntime,
@@ -31,14 +63,22 @@ import { · 1 unmodified line
31 provideExecutorRuntime,63 provideExecutorRuntime,
32 RuntimeExecutionResolverService,64 RuntimeExecutionResolverService,
33} from "./index";65} from "./index";
34import { createSourceFromPayload } from "./sources/source-definitions";66import {
35import { decodeSourceCredentialSelectionContent } from "./sources/source-credential-interactions";67 createSourceFromPayload,
36import { persistSource } from "./sources/source-store";68} from "./sources/source-definitions";
69import {
70 decodeSourceCredentialSelectionContent,
71} from "./sources/source-credential-interactions";
72import {
73 persistSource,
74} from "./sources/source-store";
37import {75import {
38 withExecutorApiClient,76 withExecutorApiClient,
39 withExecutorApiRequestHandler,77 withExecutorApiRequestHandler,
40} from "./execution/test-http-client";78} from "./execution/test-http-client";
41import { runtimeEffectError } from "./effect-errors";79import {
80 runtimeEffectError,
81} from "./effect-errors";
42import {82import {
43 buildLocalSourceArtifact,83 buildLocalSourceArtifact,
44 createLocalExecutorRuntime as createExecutorRuntime,84 createLocalExecutorRuntime as createExecutorRuntime,
@@ -57,15 +97,15 @@ import { · 12 unmodified lines
57 97
58const makeRuntime = Effect.gen(function* () {98const makeRuntime = Effect.gen(function* () {
59 const fs = yield* FileSystem.FileSystem;99 const fs = yield* FileSystem.FileSystem;
60 const workspaceRoot = yield* fs.makeTempDirectoryScoped({100 const scopeRoot = yield* fs.makeTempDirectoryScoped({
61 prefix: "executor-runtime-",101 prefix: "executor-runtime-",
62 });102 });
63 const homeConfigPath = join(workspaceRoot, ".executor-home.jsonc");103 const homeConfigPath = join(scopeRoot, ".executor-home.jsonc");
64 const homeStateDirectory = join(workspaceRoot, ".executor-home-state");104 const homeStateDirectory = join(scopeRoot, ".executor-home-state");
65 105
66 return yield* Effect.acquireRelease(106 return yield* Effect.acquireRelease(
67 createExecutorRuntime({107 createExecutorRuntime({
68 workspaceRoot,108 workspaceRoot: scopeRoot,
69 homeConfigPath,109 homeConfigPath,
70 homeStateDirectory,110 homeStateDirectory,
71 createInternalToolMap: createWorkspaceExecutorAdminToolMap,111 createInternalToolMap: createWorkspaceExecutorAdminToolMap,
@@ -74,11 +114,19 @@ const makeRuntime = Effect.gen(function* () { · 2 unmodified lines
74 );114 );
75}).pipe(Effect.provide(NodeFileSystem.layer));115}).pipe(Effect.provide(NodeFileSystem.layer));
76 116
77type OpenApiSpecServer = {117const ownerParam = HttpApiSchema.param("owner", Schema.String);
78 baseUrl: string;118const repoParam = HttpApiSchema.param("repo", Schema.String);
79 specUrl: string;119
80 close: () => Promise<void>;120class RuntimeTestReposApi extends HttpApiGroup.make("repos")
81};121 .add(
122 HttpApiEndpoint.get("getRepo")`/repos/${ownerParam}/${repoParam}`
123 .addSuccess(Schema.Unknown),
124 )
125{}
126
127class RuntimeTestOpenApi extends HttpApi.make("runtimeTest").add(
128 RuntimeTestReposApi,
129) {}
82 130
83type GoogleWorkspaceTestServer = {131type GoogleWorkspaceTestServer = {
84 baseUrl: string;132 baseUrl: string;
@@ -99,74 +147,26 @@ type GoogleWorkspaceTestServer = { · 14 unmodified lines
99 close: () => Promise<void>;147 close: () => Promise<void>;
100};148};
101 149
102const makeOpenApiSpecServer = Effect.acquireRelease(150const runtimeTestOpenApiLive = HttpApiBuilder.group(
103 Effect.promise<OpenApiSpecServer>(151 RuntimeTestOpenApi,
104 () =>152 "repos",
105 new Promise<OpenApiSpecServer>((resolve, reject) => {153 (handlers) =>
106 const openApiDocument = JSON.stringify({154 handlers.handle("getRepo", ({ path }) =>
107 openapi: "3.0.3",155 Effect.succeed({
108 info: {156 full_name: `${path.owner}/${path.repo}`,
109 title: "GitHub Test API",157 private: false,
110 version: "1.0.0",
111 },
112 paths: {
113 "/repos/{owner}/{repo}": {
114 get: {
115 operationId: "repos/get-repo",
116 tags: ["repos"],
117 summary: "Get a repository",
118 responses: {
119 200: {
120 description: "ok",
121 },
122 },
123 },
124 },
125 },
126 });
127
128 const server = createServer((request, response) => {
129 if (request.url !== "/openapi.json") {
130 response.statusCode = 404;
131 response.end();
132 return;
133 }
134
135 response.statusCode = 200;
136 response.setHeader("content-type", "application/json");
137 response.end(openApiDocument);
138 });
139
140 server.once("error", reject);
141 server.listen(0, "127.0.0.1", () => {
142 const address = server.address();
143 if (!address || typeof address === "string") {
144 reject(new Error("Failed to bind OpenAPI runtime test server"));
145 return;
146 }
147
148 const baseUrl = `http://127.0.0.1:${address.port}`;
149 resolve({
150 baseUrl,
151 specUrl: `${baseUrl}/openapi.json`,
152 close: () =>
153 new Promise<void>((closeResolve, closeReject) => {
154 server.close((error) => {
155 if (error) {
156 closeReject(error);
157 return;
158 }
159
160 closeResolve();
161 });
162 }),
163 });
164 });
165 }),158 }),
166 ),159 ),
167 (server) => Effect.promise(() => server.close()).pipe(Effect.orDie),
168);160);
169 161
162const runtimeTestOpenApiLayer = HttpApiBuilder.api(RuntimeTestOpenApi).pipe(
163 Layer.provide(runtimeTestOpenApiLive),
164);
165
166const makeOpenApiSpecServer = makeOpenApiTestServer({
167 apiLayer: runtimeTestOpenApiLayer,
168});
169
170const makeGoogleWorkspaceTestServer = Effect.acquireRelease(170const makeGoogleWorkspaceTestServer = Effect.acquireRelease(
171 Effect.promise<GoogleWorkspaceTestServer>(171 Effect.promise<GoogleWorkspaceTestServer>(
172 () =>172 () =>
@@ -379,7 +379,7 @@ const createPersistedGoogleSource = (input: { · 206 unmodified lines
379 Effect.gen(function* () {379 Effect.gen(function* () {
380 const installation = input.runtime.localInstallation;380 const installation = input.runtime.localInstallation;
381 const source = yield* createSourceFromPayload({381 const source = yield* createSourceFromPayload({
382 workspaceId: installation.workspaceId,382 scopeId: installation.scopeId,
383 sourceId: SourceIdSchema.make(`src_${crypto.randomUUID()}`),383 sourceId: SourceIdSchema.make(`src_${crypto.randomUUID()}`),
384 payload: {384 payload: {
385 name: input.name,385 name: input.name,
@@ -402,7 +402,7 @@ const createPersistedGoogleSource = (input: { · 16 unmodified lines
402 });402 });
403 403
404 return yield* persistSource(input.runtime.storage.executorState, source, {404 return yield* persistSource(input.runtime.storage.executorState, source, {
405 actorAccountId: installation.accountId,405 actorScopeId: installation.actorScopeId,
406 }).pipe((effect) => provideExecutorRuntime(effect, input.runtime));406 }).pipe((effect) => provideExecutorRuntime(effect, input.runtime));
407 });407 });
408 408
@@ -430,8 +430,8 @@ const invokeWorkspaceTool = <A>(input: { · 21 unmodified lines
430 const installation = input.runtime.localInstallation;430 const installation = input.runtime.localInstallation;
431 const resolveEnvironment = yield* RuntimeExecutionResolverService;431 const resolveEnvironment = yield* RuntimeExecutionResolverService;
432 const environment = yield* resolveEnvironment({432 const environment = yield* resolveEnvironment({
433 workspaceId: installation.workspaceId,433 scopeId: installation.scopeId,
434 accountId: installation.accountId,434 actorScopeId: installation.actorScopeId,
435 executionId: ExecutionIdSchema.make(`exec_tool_${crypto.randomUUID()}`),435 executionId: ExecutionIdSchema.make(`exec_tool_${crypto.randomUUID()}`),
436 });436 });
437 437
@@ -449,14 +449,14 @@ describe("executor-runtime", () => { · 11 unmodified lines
449 () =>449 () =>
450 Effect.gen(function* () {450 Effect.gen(function* () {
451 const fs = yield* FileSystem.FileSystem;451 const fs = yield* FileSystem.FileSystem;
452 const workspaceRoot = yield* fs.makeTempDirectoryScoped({452 const scopeRoot = yield* fs.makeTempDirectoryScoped({
453 prefix: "executor-local-config-runtime-",453 prefix: "executor-local-config-runtime-",
454 });454 });
455 const homeConfigPath = join(workspaceRoot, ".executor-home.jsonc");455 const homeConfigPath = join(scopeRoot, ".executor-home.jsonc");
456 const homeStateDirectory = join(workspaceRoot, ".executor-home-state");456 const homeStateDirectory = join(scopeRoot, ".executor-home-state");
457 const runtime = yield* Effect.acquireRelease(457 const runtime = yield* Effect.acquireRelease(
458 createExecutorRuntime({458 createExecutorRuntime({
459 workspaceRoot,459 workspaceRoot: scopeRoot,
460 homeConfigPath,460 homeConfigPath,
461 homeStateDirectory,461 homeStateDirectory,
462 }),462 }),
@@ -467,10 +467,10 @@ describe("executor-runtime", () => { · 4 unmodified lines
467 const installation = runtime.localInstallation;467 const installation = runtime.localInstallation;
468 468
469 const createdSource = yield* withExecutorApiClient(469 const createdSource = yield* withExecutorApiClient(
470 { runtime, accountId: installation.accountId },470 { runtime, actorScopeId: installation.actorScopeId },
471 (client) =>471 (client) =>
472 client.sources.create({472 client.sources.create({
473 path: { workspaceId: installation.workspaceId },473 path: { workspaceId: installation.scopeId },
474 payload: {474 payload: {
475 name: "GitHub",475 name: "GitHub",
476 kind: "openapi",476 kind: "openapi",
@@ -485,7 +485,7 @@ describe("executor-runtime", () => { · 8 unmodified lines
485 }),485 }),
486 );486 );
487 487
488 const configPath = join(workspaceRoot, ".executor", "executor.jsonc");488 const configPath = join(scopeRoot, ".executor", "executor.jsonc");
489 const createdConfig = JSON.parse(489 const createdConfig = JSON.parse(
490 yield* fs.readFileString(configPath, "utf8"),490 yield* fs.readFileString(configPath, "utf8"),
491 ) as {491 ) as {
@@ -500,11 +500,11 @@ describe("executor-runtime", () => { · 8 unmodified lines
500 );500 );
501 501
502 const removed = yield* withExecutorApiClient(502 const removed = yield* withExecutorApiClient(
503 { runtime, accountId: installation.accountId },503 { runtime, actorScopeId: installation.actorScopeId },
504 (client) =>504 (client) =>
505 client.sources.remove({505 client.sources.remove({
506 path: {506 path: {
507 workspaceId: installation.workspaceId,507 workspaceId: installation.scopeId,
508 sourceId: createdSource.id,508 sourceId: createdSource.id,
509 },509 },
510 }),510 }),
@@ -615,11 +615,11 @@ describe("executor-runtime", () => { · 104 unmodified lines
615 const openApiServer = yield* makeOpenApiSpecServer;615 const openApiServer = yield* makeOpenApiSpecServer;
616 const installation = runtime.localInstallation;616 const installation = runtime.localInstallation;
617 const createdSource = yield* withExecutorApiClient(617 const createdSource = yield* withExecutorApiClient(
618 { runtime, accountId: installation.accountId },618 { runtime, actorScopeId: installation.actorScopeId },
619 (client) =>619 (client) =>
620 client.sources.create({620 client.sources.create({
621 path: {621 path: {
622 workspaceId: installation.workspaceId,622 workspaceId: installation.scopeId,
623 },623 },
624 payload: {624 payload: {
625 name: "GitHub",625 name: "GitHub",
@@ -640,15 +640,15 @@ describe("executor-runtime", () => { · 14 unmodified lines
640 );640 );
641 641
642 const localInstallation = yield* invokeWorkspaceTool<{642 const localInstallation = yield* invokeWorkspaceTool<{
643 workspaceId: string;643 scopeId: string;
644 accountId: string;644 actorScopeId: string;
645 }>({645 }>({
646 runtime,646 runtime,
647 path: "executor.local.installation.get" as ToolPath,647 path: "executor.local.installation.get" as ToolPath,
648 args: {},648 args: {},
649 });649 });
650 expect(localInstallation.workspaceId).toBe(installation.workspaceId);650 expect(localInstallation.scopeId).toBe(installation.scopeId);
651 expect(localInstallation.accountId).toBe(installation.accountId);651 expect(localInstallation.actorScopeId).toBe(installation.actorScopeId);
652 652
653 const localConfig = yield* invokeWorkspaceTool<{653 const localConfig = yield* invokeWorkspaceTool<{
654 defaultSecretStoreProvider: string;654 defaultSecretStoreProvider: string;
@@ -723,11 +723,11 @@ describe("executor-runtime", () => { · 68 unmodified lines
723 expect(loadedPolicy.id).toBe(createdPolicy.id);723 expect(loadedPolicy.id).toBe(createdPolicy.id);
724 724
725 const listedPolicies = yield* withExecutorApiClient(725 const listedPolicies = yield* withExecutorApiClient(
726 { runtime, accountId: installation.accountId },726 { runtime, actorScopeId: installation.actorScopeId },
727 (client) =>727 (client) =>
728 client.policies.list({728 client.policies.list({
729 path: {729 path: {
730 workspaceId: installation.workspaceId,730 workspaceId: installation.scopeId,
731 },731 },
732 }),732 }),
733 );733 );
@@ -768,11 +768,11 @@ describe("executor-runtime", () => { · 34 unmodified lines
768 expect(removedSource.removed).toBe(true);768 expect(removedSource.removed).toBe(true);
769 769
770 const apiSources = yield* withExecutorApiClient(770 const apiSources = yield* withExecutorApiClient(
771 { runtime, accountId: installation.accountId },771 { runtime, actorScopeId: installation.actorScopeId },
772 (client) =>772 (client) =>
773 client.sources.list({773 client.sources.list({
774 path: {774 path: {
775 workspaceId: installation.workspaceId,775 workspaceId: installation.scopeId,
776 },776 },
777 }),777 }),
778 );778 );
@@ -798,8 +798,8 @@ describe("executor-runtime", () => { · 19 unmodified lines
798 798
799 yield* runtime.storage.executorState.executions.insert({799 yield* runtime.storage.executorState.executions.insert({
800 id: executionId,800 id: executionId,
801 workspaceId: installation.workspaceId,801 scopeId: installation.scopeId,
802 createdByAccountId: installation.accountId,802 createdByScopeId: installation.actorScopeId,
803 status: "running",803 status: "running",
804 code: "return await tools.executor.sources.add(...)",804 code: "return await tools.executor.sources.add(...)",
805 resultJson: null,805 resultJson: null,
@@ -812,7 +812,7 @@ describe("executor-runtime", () => { · 6 unmodified lines
812 });812 });
813 813
814 const localSource = yield* createSourceFromPayload({814 const localSource = yield* createSourceFromPayload({
815 workspaceId: installation.workspaceId,815 scopeId: installation.scopeId,
816 sourceId,816 sourceId,
817 payload: {817 payload: {
818 name: "GitHub",818 name: "GitHub",
@@ -832,7 +832,7 @@ describe("executor-runtime", () => { · 13 unmodified lines
832 now,832 now,
833 }).pipe(Effect.orDie);833 }).pipe(Effect.orDie);
834 yield* persistSource(runtime.storage.executorState, localSource, {834 yield* persistSource(runtime.storage.executorState, localSource, {
835 actorAccountId: installation.accountId,835 actorScopeId: installation.actorScopeId,
836 }).pipe(836 }).pipe(
837 (effect) => provideExecutorRuntime(effect, runtime),837 (effect) => provideExecutorRuntime(effect, runtime),
838 Effect.orDie,838 Effect.orDie,
@@ -854,13 +854,13 @@ describe("executor-runtime", () => { · 15 unmodified lines
854 endpoint: "https://api.github.com",854 endpoint: "https://api.github.com",
855 specUrl: "https://example.com/github-openapi.yaml",855 specUrl: "https://example.com/github-openapi.yaml",
856 name: "GitHub",856 name: "GitHub",
857 workspaceId: installation.workspaceId,857 scopeId: installation.scopeId,
858 sourceId,858 sourceId,
859 },859 },
860 elicitation: {860 elicitation: {
861 mode: "url",861 mode: "url",
862 message: "Open the secure credential page to connect GitHub",862 message: "Open the secure credential page to connect GitHub",
863 url: `http://127.0.0.1/v1/workspaces/${encodeURIComponent(installation.workspaceId)}/sources/${encodeURIComponent(sourceId)}/credentials?interactionId=${encodeURIComponent(interactionId)}`,863 url: `http://127.0.0.1/v1/workspaces/${encodeURIComponent(installation.scopeId)}/sources/${encodeURIComponent(sourceId)}/credentials?interactionId=${encodeURIComponent(interactionId)}`,
864 elicitationId: interactionSuffix,864 elicitationId: interactionSuffix,
865 },865 },
866 });866 });
@@ -893,7 +893,7 @@ describe("executor-runtime", () => { · 26 unmodified lines
893 (handleRequest) =>893 (handleRequest) =>
894 Effect.promise(async () => {894 Effect.promise(async () => {
895 const url = new URL(895 const url = new URL(
896 `/v1/workspaces/${encodeURIComponent(installation.workspaceId)}/sources/${encodeURIComponent(sourceId)}/credentials`,896 `/v1/workspaces/${encodeURIComponent(installation.scopeId)}/sources/${encodeURIComponent(sourceId)}/credentials`,
897 "http://127.0.0.1",897 "http://127.0.0.1",
898 );898 );
899 url.searchParams.set("interactionId", pendingInteraction.value.id);899 url.searchParams.set("interactionId", pendingInteraction.value.id);
@@ -910,7 +910,7 @@ describe("executor-runtime", () => { · 10 unmodified lines
910 (handleRequest) =>910 (handleRequest) =>
911 Effect.promise(async () => {911 Effect.promise(async () => {
912 const url = new URL(912 const url = new URL(
913 `/v1/workspaces/${encodeURIComponent(installation.workspaceId)}/sources/${encodeURIComponent(sourceId)}/credentials`,913 `/v1/workspaces/${encodeURIComponent(installation.scopeId)}/sources/${encodeURIComponent(sourceId)}/credentials`,
914 "http://127.0.0.1",914 "http://127.0.0.1",
915 );915 );
916 url.searchParams.set("interactionId", pendingInteraction.value.id);916 url.searchParams.set("interactionId", pendingInteraction.value.id);
@@ -978,12 +978,12 @@ describe("executor-runtime", () => { · 61 unmodified lines
978 it.scoped("loads a v1.2.3-style workspace artifact on startup", () =>978 it.scoped("loads a v1.2.3-style workspace artifact on startup", () =>
979 Effect.gen(function* () {979 Effect.gen(function* () {
980 const fs = yield* FileSystem.FileSystem;980 const fs = yield* FileSystem.FileSystem;
981 const workspaceRoot = yield* fs.makeTempDirectoryScoped({981 const scopeRoot = yield* fs.makeTempDirectoryScoped({
982 prefix: "executor-runtime-v123-",982 prefix: "executor-runtime-v123-",
983 });983 });
984 const homeConfigPath = join(workspaceRoot, ".executor-home.jsonc");984 const homeConfigPath = join(scopeRoot, ".executor-home.jsonc");
985 const homeStateDirectory = join(workspaceRoot, ".executor-home-state");985 const homeStateDirectory = join(scopeRoot, ".executor-home-state");
986 const configDirectory = join(workspaceRoot, ".executor");986 const configDirectory = join(scopeRoot, ".executor");
987 const configPath = join(configDirectory, "executor.jsonc");987 const configPath = join(configDirectory, "executor.jsonc");
988 const sourceId = SourceIdSchema.make("graphql");988 const sourceId = SourceIdSchema.make("graphql");
989 const now = Date.now();989 const now = Date.now();
@@ -1010,13 +1010,13 @@ describe("executor-runtime", () => { · 20 unmodified lines
1010 );1010 );
1011 1011
1012 const context = yield* resolveLocalWorkspaceContext({1012 const context = yield* resolveLocalWorkspaceContext({
1013 workspaceRoot,1013 workspaceRoot: scopeRoot,
1014 homeConfigPath,1014 homeConfigPath,
1015 homeStateDirectory,1015 homeStateDirectory,
1016 });1016 });
1017 const installation = deriveLocalInstallation(context);1017 const installation = deriveLocalInstallation(context);
1018 const source = yield* createSourceFromPayload({1018 const source = yield* createSourceFromPayload({
1019 workspaceId: installation.workspaceId,1019 scopeId: installation.scopeId,
1020 sourceId,1020 sourceId,
1021 payload: {1021 payload: {
1022 name: "GraphQL API",1022 name: "GraphQL API",
@@ -1108,7 +1108,7 @@ describe("executor-runtime", () => { · 85 unmodified lines
1108 authArtifacts: [],1108 authArtifacts: [],
1109 authLeases: [],1109 authLeases: [],
1110 sourceOauthClients: [],1110 sourceOauthClients: [],
1111 workspaceOauthClients: [],1111 scopeOauthClients: [],
1112 providerAuthGrants: [],1112 providerAuthGrants: [],
1113 sourceAuthSessions: [],1113 sourceAuthSessions: [],
1114 secretMaterials: [],1114 secretMaterials: [],
@@ -1145,7 +1145,7 @@ describe("executor-runtime", () => { · 30 unmodified lines
1145 1145
1146 const runtime = yield* Effect.acquireRelease(1146 const runtime = yield* Effect.acquireRelease(
1147 createExecutorRuntime({1147 createExecutorRuntime({
1148 workspaceRoot,1148 workspaceRoot: scopeRoot,
1149 homeConfigPath,1149 homeConfigPath,
1150 homeStateDirectory,1150 homeStateDirectory,
1151 }),1151 }),
@@ -1154,11 +1154,11 @@ describe("executor-runtime", () => { · 2 unmodified lines
1154 );1154 );
1155 1155
1156 const inspection = yield* withExecutorApiClient(1156 const inspection = yield* withExecutorApiClient(
1157 { runtime, accountId: installation.accountId },1157 { runtime, actorScopeId: installation.actorScopeId },
1158 (client) =>1158 (client) =>
1159 client.sources.inspection({1159 client.sources.inspection({
1160 path: {1160 path: {
1161 workspaceId: installation.workspaceId,1161 workspaceId: installation.scopeId,
1162 sourceId,1162 sourceId,
1163 },1163 },
1164 }),1164 }),
@@ -1179,13 +1179,13 @@ describe("executor-runtime", () => { · 14 unmodified lines
1179 Effect.gen(function* () {1179 Effect.gen(function* () {
1180 const fs = yield* FileSystem.FileSystem;1180 const fs = yield* FileSystem.FileSystem;
1181 const openApiServer = yield* makeOpenApiSpecServer;1181 const openApiServer = yield* makeOpenApiSpecServer;
1182 const workspaceRoot = yield* fs.makeTempDirectoryScoped({1182 const scopeRoot = yield* fs.makeTempDirectoryScoped({
1183 prefix: "executor-runtime-startup-rebuild-",1183 prefix: "executor-runtime-startup-rebuild-",
1184 });1184 });
1185 const homeConfigPath = join(workspaceRoot, ".executor-home.jsonc");1185 const homeConfigPath = join(scopeRoot, ".executor-home.jsonc");
1186 const homeStateDirectory = join(workspaceRoot, ".executor-home-state");1186 const homeStateDirectory = join(scopeRoot, ".executor-home-state");
1187 const context = yield* resolveLocalWorkspaceContext({1187 const context = yield* resolveLocalWorkspaceContext({
1188 workspaceRoot,1188 workspaceRoot: scopeRoot,
1189 homeConfigPath,1189 homeConfigPath,
1190 homeStateDirectory,1190 homeStateDirectory,
1191 });1191 });
@@ -1193,7 +1193,7 @@ describe("executor-runtime", () => { · 1 unmodified line
1193 const sourceId = SourceIdSchema.make("github");1193 const sourceId = SourceIdSchema.make("github");
1194 const now = Date.now();1194 const now = Date.now();
1195 const source = yield* createSourceFromPayload({1195 const source = yield* createSourceFromPayload({
1196 workspaceId: installation.workspaceId,1196 scopeId: installation.scopeId,
1197 sourceId,1197 sourceId,
1198 payload: {1198 payload: {
1199 name: "GitHub",1199 name: "GitHub",
@@ -1251,7 +1251,7 @@ describe("executor-runtime", () => { · 51 unmodified lines
1251 1251
1252 const runtime = yield* Effect.acquireRelease(1252 const runtime = yield* Effect.acquireRelease(
1253 createExecutorRuntime({1253 createExecutorRuntime({
1254 workspaceRoot,1254 workspaceRoot: scopeRoot,
1255 homeConfigPath,1255 homeConfigPath,
1256 homeStateDirectory,1256 homeStateDirectory,
1257 }),1257 }),
@@ -1266,11 +1266,11 @@ describe("executor-runtime", () => { · 8 unmodified lines
1266 expect(rebuiltArtifact).not.toBeNull();1266 expect(rebuiltArtifact).not.toBeNull();
1267 1267
1268 const inspection = yield* withExecutorApiClient(1268 const inspection = yield* withExecutorApiClient(
1269 { runtime, accountId: installation.accountId },1269 { runtime, actorScopeId: installation.actorScopeId },
1270 (client) =>1270 (client) =>
1271 client.sources.inspection({1271 client.sources.inspection({
1272 path: {1272 path: {
1273 workspaceId: installation.workspaceId,1273 workspaceId: installation.scopeId,
1274 sourceId,1274 sourceId,
1275 },1275 },
1276 }),1276 }),
@@ -1290,7 +1290,7 @@ describe("executor-runtime", () => { · 13 unmodified lines
1290 const now = Date.now();1290 const now = Date.now();
1291 1291
1292 const localSource = yield* createSourceFromPayload({1292 const localSource = yield* createSourceFromPayload({
1293 workspaceId: installation.workspaceId,1293 scopeId: installation.scopeId,
1294 sourceId,1294 sourceId,
1295 payload: {1295 payload: {
1296 name: "Google Drive",1296 name: "Google Drive",
@@ -1314,18 +1314,18 @@ describe("executor-runtime", () => { · 17 unmodified lines
1314 now,1314 now,
1315 }).pipe(Effect.orDie);1315 }).pipe(Effect.orDie);
1316 yield* persistSource(runtime.storage.executorState, localSource, {1316 yield* persistSource(runtime.storage.executorState, localSource, {
1317 actorAccountId: installation.accountId,1317 actorScopeId: installation.actorScopeId,
1318 }).pipe(1318 }).pipe(
1319 (effect) => provideExecutorRuntime(effect, runtime),1319 (effect) => provideExecutorRuntime(effect, runtime),
1320 Effect.orDie,1320 Effect.orDie,
1321 );1321 );
1322 1322
1323 const inspection = yield* withExecutorApiClient(1323 const inspection = yield* withExecutorApiClient(
1324 { runtime, accountId: installation.accountId },1324 { runtime, actorScopeId: installation.actorScopeId },
1325 (client) =>1325 (client) =>
1326 client.sources.inspection({1326 client.sources.inspection({
1327 path: {1327 path: {
1328 workspaceId: installation.workspaceId,1328 workspaceId: installation.scopeId,
1329 sourceId,1329 sourceId,
1330 },1330 },
1331 }),1331 }),
@@ -1348,7 +1348,7 @@ describe("executor-runtime", () => { · 16 unmodified lines
1348 const now = Date.now();1348 const now = Date.now();
1349 1349
1350 const localSource = yield* createSourceFromPayload({1350 const localSource = yield* createSourceFromPayload({
1351 workspaceId: installation.workspaceId,1351 scopeId: installation.scopeId,
1352 sourceId,1352 sourceId,
1353 payload: {1353 payload: {
1354 name: "Google Drive",1354 name: "Google Drive",
@@ -1372,7 +1372,7 @@ describe("executor-runtime", () => { · 17 unmodified lines
1372 now,1372 now,
1373 }).pipe(Effect.orDie);1373 }).pipe(Effect.orDie);
1374 yield* persistSource(runtime.storage.executorState, localSource, {1374 yield* persistSource(runtime.storage.executorState, localSource, {
1375 actorAccountId: installation.accountId,1375 actorScopeId: installation.actorScopeId,
1376 }).pipe(1376 }).pipe(
1377 (effect) => provideExecutorRuntime(effect, runtime),1377 (effect) => provideExecutorRuntime(effect, runtime),
1378 Effect.orDie,1378 Effect.orDie,
@@ -1380,11 +1380,11 @@ describe("executor-runtime", () => { · 1 unmodified line
1380 1380
1381 const error = yield* expectLeft(1381 const error = yield* expectLeft(
1382 withExecutorApiClient(1382 withExecutorApiClient(
1383 { runtime, accountId: installation.accountId },1383 { runtime, actorScopeId: installation.actorScopeId },
1384 (client) =>1384 (client) =>
1385 client.sources.inspection({1385 client.sources.inspection({
1386 path: {1386 path: {
1387 workspaceId: installation.workspaceId,1387 workspaceId: installation.scopeId,
1388 sourceId,1388 sourceId,
1389 },1389 },
1390 }),1390 }),
@@ -1415,8 +1415,8 @@ describe("executor-runtime", () => { · 24 unmodified lines
1415 1415
1416 yield* runtime.storage.executorState.executions.insert({1416 yield* runtime.storage.executorState.executions.insert({
1417 id: executionId,1417 id: executionId,
1418 workspaceId: installation.workspaceId,1418 scopeId: installation.scopeId,
1419 createdByAccountId: installation.accountId,1419 createdByScopeId: installation.actorScopeId,
1420 status: "running",1420 status: "running",
1421 code: "return await tools.executor.sources.add(...)",1421 code: "return await tools.executor.sources.add(...)",
1422 resultJson: null,1422 resultJson: null,
@@ -1429,7 +1429,7 @@ describe("executor-runtime", () => { · 6 unmodified lines
1429 });1429 });
1430 1430
1431 const localSource = yield* createSourceFromPayload({1431 const localSource = yield* createSourceFromPayload({
1432 workspaceId: installation.workspaceId,1432 scopeId: installation.scopeId,
1433 sourceId,1433 sourceId,
1434 payload: {1434 payload: {
1435 name: "GitHub",1435 name: "GitHub",
@@ -1449,7 +1449,7 @@ describe("executor-runtime", () => { · 13 unmodified lines
1449 now,1449 now,
1450 }).pipe(Effect.orDie);1450 }).pipe(Effect.orDie);
1451 yield* persistSource(runtime.storage.executorState, localSource, {1451 yield* persistSource(runtime.storage.executorState, localSource, {
1452 actorAccountId: installation.accountId,1452 actorScopeId: installation.actorScopeId,
1453 }).pipe(1453 }).pipe(
1454 (effect) => provideExecutorRuntime(effect, runtime),1454 (effect) => provideExecutorRuntime(effect, runtime),
1455 Effect.orDie,1455 Effect.orDie,
@@ -1471,13 +1471,13 @@ describe("executor-runtime", () => { · 15 unmodified lines
1471 endpoint: "https://api.github.com",1471 endpoint: "https://api.github.com",
1472 specUrl: "https://example.com/github-openapi.yaml",1472 specUrl: "https://example.com/github-openapi.yaml",
1473 name: "GitHub",1473 name: "GitHub",
1474 workspaceId: installation.workspaceId,1474 scopeId: installation.scopeId,
1475 sourceId,1475 sourceId,
1476 },1476 },
1477 elicitation: {1477 elicitation: {
1478 mode: "url",1478 mode: "url",
1479 message: "Open the secure credential page to connect GitHub",1479 message: "Open the secure credential page to connect GitHub",
1480 url: `http://127.0.0.1/v1/workspaces/${encodeURIComponent(installation.workspaceId)}/sources/${encodeURIComponent(sourceId)}/credentials?interactionId=${encodeURIComponent(interactionId)}`,1480 url: `http://127.0.0.1/v1/workspaces/${encodeURIComponent(installation.scopeId)}/sources/${encodeURIComponent(sourceId)}/credentials?interactionId=${encodeURIComponent(interactionId)}`,
1481 elicitationId: interactionSuffix,1481 elicitationId: interactionSuffix,
1482 },1482 },
1483 });1483 });
@@ -1505,7 +1505,7 @@ describe("executor-runtime", () => { · 21 unmodified lines
1505 assertTrue(Option.isSome(pendingInteraction));1505 assertTrue(Option.isSome(pendingInteraction));
1506 1506
1507 const submitted = yield* submitSourceCredentialInteraction({1507 const submitted = yield* submitSourceCredentialInteraction({
1508 workspaceId: installation.workspaceId,1508 scopeId: installation.scopeId,
1509 sourceId,1509 sourceId,
1510 interactionId: pendingInteraction.value.id,1510 interactionId: pendingInteraction.value.id,
1511 action: "continue",1511 action: "continue",
@@ -1538,7 +1538,7 @@ describe("executor-runtime", () => { · 26 unmodified lines
1538 const executionId = ExecutionIdSchema.make(1538 const executionId = ExecutionIdSchema.make(
1539 "exec_local_graphql_credential_continue",1539 "exec_local_graphql_credential_continue",
1540 );1540 );
1541 const workspaceId = "ws_local_graphql" as const;1541 const scopeId = "ws_local_graphql" as const;
1542 const sourceId = SourceIdSchema.make("graphql_api");1542 const sourceId = SourceIdSchema.make("graphql_api");
1543 const now = Date.now();1543 const now = Date.now();
1544 const decoded = decodeSourceCredentialInteraction({1544 const decoded = decodeSourceCredentialInteraction({
@@ -1555,7 +1555,7 @@ describe("executor-runtime", () => { · 10 unmodified lines
1555 endpoint: "https://example.com/graphql",1555 endpoint: "https://example.com/graphql",
1556 name: "GraphQL API",1556 name: "GraphQL API",
1557 namespace: "graphql",1557 namespace: "graphql",
1558 workspaceId,1558 scopeId,
1559 sourceId,1559 sourceId,
1560 },1560 },
1561 elicitation: {1561 elicitation: {
@@ -1570,7 +1570,7 @@ describe("executor-runtime", () => { · 8 unmodified lines
1570 });1570 });
1571 1571
1572 expect(decoded).not.toBeNull();1572 expect(decoded).not.toBeNull();
1573 expect(decoded?.workspaceId).toBe(workspaceId);1573 expect(decoded?.scopeId).toBe(scopeId);
1574 expect(decoded?.sourceId).toBe(sourceId);1574 expect(decoded?.sourceId).toBe(sourceId);
1575 expect(decoded?.message).toContain("GraphQL API");1575 expect(decoded?.message).toContain("GraphQL API");
1576 }),1576 }),
@@ -1589,7 +1589,7 @@ describe("executor-runtime", () => { · 12 unmodified lines
1589 (client) =>1589 (client) =>
1590 client.sources.createWorkspaceOauthClient({1590 client.sources.createWorkspaceOauthClient({
1591 path: {1591 path: {
1592 workspaceId: installation.workspaceId,1592 workspaceId: installation.scopeId,
1593 },1593 },
1594 payload: {1594 payload: {
1595 providerKey: "google_workspace",1595 providerKey: "google_workspace",
@@ -1615,8 +1615,8 @@ describe("executor-runtime", () => { · 19 unmodified lines
1615 );1615 );
1616 yield* runtime.storage.executorState.providerAuthGrants.upsert({1616 yield* runtime.storage.executorState.providerAuthGrants.upsert({
1617 id: grantId,1617 id: grantId,
1618 workspaceId: installation.workspaceId,1618 scopeId: installation.scopeId,
1619 actorAccountId: installation.accountId,1619 actorScopeId: installation.actorScopeId,
1620 providerKey: "google_workspace",1620 providerKey: "google_workspace",
1621 oauthClientId: oauthClient.id,1621 oauthClientId: oauthClient.id,
1622 tokenEndpoint: googleServer.tokenEndpoint,1622 tokenEndpoint: googleServer.tokenEndpoint,
@@ -1642,11 +1642,11 @@ describe("executor-runtime", () => { · 19 unmodified lines
1642 const result = yield* withExecutorApiClient({ runtime }, (client) =>1642 const result = yield* withExecutorApiClient({ runtime }, (client) =>
1643 client.sources.connect({1643 client.sources.connect({
1644 path: {1644 path: {
1645 workspaceId: installation.workspaceId,1645 workspaceId: installation.scopeId,
1646 },1646 },
1647 payload: {1647 payload: {
1648 kind: "google_discovery",1648 kind: "google_discovery",
1649 workspaceOauthClientId: oauthClient.id,1649 scopeOauthClientId: oauthClient.id,
1650 service: "drive",1650 service: "drive",
1651 version: "v3",1651 version: "v3",
1652 discoveryUrl: googleServer.discoveryUrl({1652 discoveryUrl: googleServer.discoveryUrl({
@@ -1698,7 +1698,7 @@ describe("executor-runtime", () => { · 45 unmodified lines
1698 (client) =>1698 (client) =>
1699 client.sources.createWorkspaceOauthClient({1699 client.sources.createWorkspaceOauthClient({
1700 path: {1700 path: {
1701 workspaceId: installation.workspaceId,1701 workspaceId: installation.scopeId,
1702 },1702 },
1703 payload: {1703 payload: {
1704 providerKey: "google_workspace",1704 providerKey: "google_workspace",
@@ -1714,11 +1714,11 @@ describe("executor-runtime", () => { · 9 unmodified lines
1714 const result = yield* withExecutorApiClient({ runtime }, (client) =>1714 const result = yield* withExecutorApiClient({ runtime }, (client) =>
1715 client.sources.connect({1715 client.sources.connect({
1716 path: {1716 path: {
1717 workspaceId: installation.workspaceId,1717 workspaceId: installation.scopeId,
1718 },1718 },
1719 payload: {1719 payload: {
1720 kind: "google_discovery",1720 kind: "google_discovery",
1721 workspaceOauthClientId: oauthClient.id,1721 scopeOauthClientId: oauthClient.id,
1722 service: "gmail",1722 service: "gmail",
1723 version: "v1",1723 version: "v1",
1724 discoveryUrl: googleServer.discoveryUrl({1724 discoveryUrl: googleServer.discoveryUrl({
@@ -1745,7 +1745,7 @@ describe("executor-runtime", () => { · 20 unmodified lines
1745 );1745 );
1746 expect(redirectUri).not.toBeNull();1746 expect(redirectUri).not.toBeNull();
1747 expect(new URL(redirectUri!).pathname).toBe(1747 expect(new URL(redirectUri!).pathname).toBe(
1748 `/v1/workspaces/${encodeURIComponent(installation.workspaceId)}/oauth/provider/callback`,1748 `/v1/workspaces/${encodeURIComponent(installation.scopeId)}/oauth/provider/callback`,
1749 );1749 );
1750 expect(result.source.status).toBe("auth_required");1750 expect(result.source.status).toBe("auth_required");
1751 }),1751 }),
@@ -1765,7 +1765,7 @@ describe("executor-runtime", () => { · 13 unmodified lines
1765 (client) =>1765 (client) =>
1766 client.sources.createWorkspaceOauthClient({1766 client.sources.createWorkspaceOauthClient({
1767 path: {1767 path: {
1768 workspaceId: installation.workspaceId,1768 workspaceId: installation.scopeId,
1769 },1769 },
1770 payload: {1770 payload: {
1771 providerKey: "google_workspace",1771 providerKey: "google_workspace",
@@ -1791,8 +1791,8 @@ describe("executor-runtime", () => { · 19 unmodified lines
1791 );1791 );
1792 yield* runtime.storage.executorState.providerAuthGrants.upsert({1792 yield* runtime.storage.executorState.providerAuthGrants.upsert({
1793 id: grantId,1793 id: grantId,
1794 workspaceId: installation.workspaceId,1794 scopeId: installation.scopeId,
1795 actorAccountId: installation.accountId,1795 actorScopeId: installation.actorScopeId,
1796 providerKey: "google_workspace",1796 providerKey: "google_workspace",
1797 oauthClientId: oauthClient.id,1797 oauthClientId: oauthClient.id,
1798 tokenEndpoint: googleServer.tokenEndpoint,1798 tokenEndpoint: googleServer.tokenEndpoint,
@@ -1822,10 +1822,10 @@ describe("executor-runtime", () => { · 23 unmodified lines
1822 const result = yield* withExecutorApiClient({ runtime }, (client) =>1822 const result = yield* withExecutorApiClient({ runtime }, (client) =>
1823 client.sources.connectBatch({1823 client.sources.connectBatch({
1824 path: {1824 path: {
1825 workspaceId: installation.workspaceId,1825 workspaceId: installation.scopeId,
1826 },1826 },
1827 payload: {1827 payload: {
1828 workspaceOauthClientId: oauthClient.id,1828 scopeOauthClientId: oauthClient.id,
1829 sources: [1829 sources: [
1830 {1830 {
1831 service: "gmail",1831 service: "gmail",
@@ -1894,7 +1894,7 @@ describe("executor-runtime", () => { · 62 unmodified lines
1894 (client) =>1894 (client) =>
1895 client.sources.createWorkspaceOauthClient({1895 client.sources.createWorkspaceOauthClient({
1896 path: {1896 path: {
1897 workspaceId: installation.workspaceId,1897 workspaceId: installation.scopeId,
1898 },1898 },
1899 payload: {1899 payload: {
1900 providerKey: "google_workspace",1900 providerKey: "google_workspace",
@@ -1910,10 +1910,10 @@ describe("executor-runtime", () => { · 9 unmodified lines
1910 const result = yield* withExecutorApiClient({ runtime }, (client) =>1910 const result = yield* withExecutorApiClient({ runtime }, (client) =>
1911 client.sources.connectBatch({1911 client.sources.connectBatch({
1912 path: {1912 path: {
1913 workspaceId: installation.workspaceId,1913 workspaceId: installation.scopeId,
1914 },1914 },
1915 payload: {1915 payload: {
1916 workspaceOauthClientId: oauthClient.id,1916 scopeOauthClientId: oauthClient.id,
1917 sources: [1917 sources: [
1918 {1918 {
1919 service: "gmail",1919 service: "gmail",
@@ -1939,7 +1939,7 @@ describe("executor-runtime", () => { · 19 unmodified lines
1939 1939
1940 expect(redirectUri).not.toBeNull();1940 expect(redirectUri).not.toBeNull();
1941 expect(new URL(redirectUri!).pathname).toBe(1941 expect(new URL(redirectUri!).pathname).toBe(
1942 `/v1/workspaces/${encodeURIComponent(installation.workspaceId)}/oauth/provider/callback`,1942 `/v1/workspaces/${encodeURIComponent(installation.scopeId)}/oauth/provider/callback`,
1943 );1943 );
1944 }),1944 }),
1945 60_000,1945 60_000,
@@ -1958,7 +1958,7 @@ describe("executor-runtime", () => { · 12 unmodified lines
1958 (client) =>1958 (client) =>
1959 client.sources.createWorkspaceOauthClient({1959 client.sources.createWorkspaceOauthClient({
1960 path: {1960 path: {
1961 workspaceId: installation.workspaceId,1961 workspaceId: installation.scopeId,
1962 },1962 },
1963 payload: {1963 payload: {
1964 providerKey: "google_workspace",1964 providerKey: "google_workspace",
@@ -2001,8 +2001,8 @@ describe("executor-runtime", () => { · 36 unmodified lines
2001 );2001 );
2002 yield* runtime.storage.executorState.providerAuthGrants.upsert({2002 yield* runtime.storage.executorState.providerAuthGrants.upsert({
2003 id: grantId,2003 id: grantId,
2004 workspaceId: installation.workspaceId,2004 scopeId: installation.scopeId,
2005 actorAccountId: installation.accountId,2005 actorScopeId: installation.actorScopeId,
2006 providerKey: "google_workspace",2006 providerKey: "google_workspace",
2007 oauthClientId: oauthClient.id,2007 oauthClientId: oauthClient.id,
2008 tokenEndpoint: googleServer.tokenEndpoint,2008 tokenEndpoint: googleServer.tokenEndpoint,
@@ -2026,11 +2026,11 @@ describe("executor-runtime", () => { · 17 unmodified lines
2026 const state = `provider-state-${crypto.randomUUID()}`;2026 const state = `provider-state-${crypto.randomUUID()}`;
2027 yield* runtime.storage.executorState.sourceAuthSessions.upsert({2027 yield* runtime.storage.executorState.sourceAuthSessions.upsert({
2028 id: sessionId,2028 id: sessionId,
2029 workspaceId: installation.workspaceId,2029 scopeId: installation.scopeId,
2030 sourceId: SourceIdSchema.make(2030 sourceId: SourceIdSchema.make(
2031 `oauth_provider_${crypto.randomUUID()}`,2031 `oauth_provider_${crypto.randomUUID()}`,
2032 ),2032 ),
2033 actorAccountId: installation.accountId,2033 actorScopeId: installation.actorScopeId,
2034 credentialSlot: "runtime",2034 credentialSlot: "runtime",
2035 executionId: null,2035 executionId: null,
2036 interactionId: null,2036 interactionId: null,
@@ -2083,7 +2083,7 @@ describe("executor-runtime", () => { · 46 unmodified lines
2083 (client) =>2083 (client) =>
2084 client.sources.providerOauthComplete({2084 client.sources.providerOauthComplete({
2085 path: {2085 path: {
2086 workspaceId: installation.workspaceId,2086 workspaceId: installation.scopeId,
2087 },2087 },
2088 urlParams: {2088 urlParams: {
2089 state,2089 state,
@@ -2111,7 +2111,7 @@ describe("executor-runtime", () => { · 21 unmodified lines
2111 (client) =>2111 (client) =>
2112 client.sources.get({2112 client.sources.get({
2113 path: {2113 path: {
2114 workspaceId: installation.workspaceId,2114 workspaceId: installation.scopeId,
2115 sourceId: source.id,2115 sourceId: source.id,
2116 },2116 },
2117 }),2117 }),
@@ -2148,14 +2148,14 @@ describe("executor-runtime", () => { · 30 unmodified lines
2148 Effect.gen(function* () {2148 Effect.gen(function* () {
2149 const runtime = yield* makeRuntime;2149 const runtime = yield* makeRuntime;
2150 const installation = runtime.localInstallation;2150 const installation = runtime.localInstallation;
2151 const oauthClientId = WorkspaceOauthClientIdSchema.make(2151 const oauthClientId = ScopeOauthClientIdSchema.make(
2152 `ws_oauth_client_${crypto.randomUUID()}`,2152 `ws_oauth_client_${crypto.randomUUID()}`,
2153 );2153 );
2154 const refreshSecretId = "sec_google_refresh_orphan";2154 const refreshSecretId = "sec_google_refresh_orphan";
2155 2155
2156 yield* runtime.storage.executorState.workspaceOauthClients.upsert({2156 yield* runtime.storage.executorState.scopeOauthClients.upsert({
2157 id: oauthClientId,2157 id: oauthClientId,
2158 workspaceId: installation.workspaceId,2158 scopeId: installation.scopeId,
2159 providerKey: "google_workspace",2159 providerKey: "google_workspace",
2160 label: "Local Google Workspace Client",2160 label: "Local Google Workspace Client",
2161 clientId: "google-client-id",2161 clientId: "google-client-id",
@@ -2177,8 +2177,8 @@ describe("executor-runtime", () => { · 15 unmodified lines
2177 );2177 );
2178 yield* runtime.storage.executorState.providerAuthGrants.upsert({2178 yield* runtime.storage.executorState.providerAuthGrants.upsert({
2179 id: grantId,2179 id: grantId,
2180 workspaceId: installation.workspaceId,2180 scopeId: installation.scopeId,
2181 actorAccountId: installation.accountId,2181 actorScopeId: installation.actorScopeId,
2182 providerKey: "google_workspace",2182 providerKey: "google_workspace",
2183 oauthClientId,2183 oauthClientId,
2184 tokenEndpoint: "https://example.test/oauth/token",2184 tokenEndpoint: "https://example.test/oauth/token",
@@ -2217,7 +2217,7 @@ describe("executor-runtime", () => { · 32 unmodified lines
2217 const removed = yield* withExecutorApiClient({ runtime }, (client) =>2217 const removed = yield* withExecutorApiClient({ runtime }, (client) =>
2218 client.sources.remove({2218 client.sources.remove({
2219 path: {2219 path: {
2220 workspaceId: installation.workspaceId,2220 workspaceId: installation.scopeId,
2221 sourceId: source.id,2221 sourceId: source.id,
2222 },2222 },
2223 }),2223 }),
@@ -2238,14 +2238,14 @@ describe("executor-runtime", () => { · 14 unmodified lines
2238 Effect.gen(function* () {2238 Effect.gen(function* () {
2239 const runtime = yield* makeRuntime;2239 const runtime = yield* makeRuntime;
2240 const installation = runtime.localInstallation;2240 const installation = runtime.localInstallation;
2241 const oauthClientId = WorkspaceOauthClientIdSchema.make(2241 const oauthClientId = ScopeOauthClientIdSchema.make(
2242 `ws_oauth_client_${crypto.randomUUID()}`,2242 `ws_oauth_client_${crypto.randomUUID()}`,
2243 );2243 );
2244 const refreshSecretId = "sec_google_refresh_revoke";2244 const refreshSecretId = "sec_google_refresh_revoke";
2245 2245
2246 yield* runtime.storage.executorState.workspaceOauthClients.upsert({2246 yield* runtime.storage.executorState.scopeOauthClients.upsert({
2247 id: oauthClientId,2247 id: oauthClientId,
2248 workspaceId: installation.workspaceId,2248 scopeId: installation.scopeId,
2249 providerKey: "google_workspace",2249 providerKey: "google_workspace",
2250 label: "Local Google Workspace Client",2250 label: "Local Google Workspace Client",
2251 clientId: "google-client-id",2251 clientId: "google-client-id",
@@ -2267,8 +2267,8 @@ describe("executor-runtime", () => { · 15 unmodified lines
2267 );2267 );
2268 yield* runtime.storage.executorState.providerAuthGrants.upsert({2268 yield* runtime.storage.executorState.providerAuthGrants.upsert({
2269 id: grantId,2269 id: grantId,
2270 workspaceId: installation.workspaceId,2270 scopeId: installation.scopeId,
2271 actorAccountId: installation.accountId,2271 actorScopeId: installation.actorScopeId,
2272 providerKey: "google_workspace",2272 providerKey: "google_workspace",
2273 oauthClientId,2273 oauthClientId,
2274 tokenEndpoint: "https://example.test/oauth/token",2274 tokenEndpoint: "https://example.test/oauth/token",
@@ -2324,7 +2324,7 @@ describe("executor-runtime", () => { · 49 unmodified lines
2324 const removed = yield* withExecutorApiClient({ runtime }, (client) =>2324 const removed = yield* withExecutorApiClient({ runtime }, (client) =>
2325 client.sources.removeProviderAuthGrant({2325 client.sources.removeProviderAuthGrant({
2326 path: {2326 path: {
2327 workspaceId: installation.workspaceId,2327 workspaceId: installation.scopeId,
2328 grantId,2328 grantId,
2329 },2329 },
2330 }),2330 }),
@@ -2347,7 +2347,7 @@ describe("executor-runtime", () => { · 16 unmodified lines
2347 (client) =>2347 (client) =>
2348 client.sources.get({2348 client.sources.get({
2349 path: {2349 path: {
2350 workspaceId: installation.workspaceId,2350 workspaceId: installation.scopeId,
2351 sourceId: driveSource.id,2351 sourceId: driveSource.id,
2352 },2352 },
2353 }),2353 }),
@@ -2357,7 +2357,7 @@ describe("executor-runtime", () => { · 3 unmodified lines
2357 (client) =>2357 (client) =>
2358 client.sources.get({2358 client.sources.get({
2359 path: {2359 path: {
2360 workspaceId: installation.workspaceId,2360 workspaceId: installation.scopeId,
2361 sourceId: gmailSource.id,2361 sourceId: gmailSource.id,
2362 },2362 },
2363 }),2363 }),
packages/platform/sdk/src/runtime/executor-state-store.ts+46−46
@@ -10,8 +10,8 @@ import type { · 9 unmodified lines
10 ProviderAuthGrant,10 ProviderAuthGrant,
11 SecretMaterial,11 SecretMaterial,
12 SourceAuthSession,12 SourceAuthSession,
13 WorkspaceOauthClient,13 ScopeOauthClient,
14 WorkspaceSourceOauthClient,14 ScopedSourceOauthClient,
15} from "#schema";15} from "#schema";
16 16
17type SecretMaterialSummary = {17type SecretMaterialSummary = {
@@ -25,28 +25,28 @@ type SecretMaterialSummary = { · 7 unmodified lines
25 25
26export type ExecutorStateStoreShape = {26export type ExecutorStateStoreShape = {
27 authArtifacts: {27 authArtifacts: {
28 listByWorkspaceId: (28 listByScopeId: (
29 workspaceId: AuthArtifact["workspaceId"],29 scopeId: AuthArtifact["scopeId"],
30 ) => Effect.Effect<readonly AuthArtifact[], Error, never>;30 ) => Effect.Effect<readonly AuthArtifact[], Error, never>;
31 listByWorkspaceAndSourceId: (input: {31 listByScopeAndSourceId: (input: {
32 workspaceId: AuthArtifact["workspaceId"];32 scopeId: AuthArtifact["scopeId"];
33 sourceId: AuthArtifact["sourceId"];33 sourceId: AuthArtifact["sourceId"];
34 }) => Effect.Effect<readonly AuthArtifact[], Error, never>;34 }) => Effect.Effect<readonly AuthArtifact[], Error, never>;
35 getByWorkspaceSourceAndActor: (input: {35 getByScopeSourceAndActor: (input: {
36 workspaceId: AuthArtifact["workspaceId"];36 scopeId: AuthArtifact["scopeId"];
37 sourceId: AuthArtifact["sourceId"];37 sourceId: AuthArtifact["sourceId"];
38 actorAccountId: AuthArtifact["actorAccountId"];38 actorScopeId: AuthArtifact["actorScopeId"];
39 slot: AuthArtifact["slot"];39 slot: AuthArtifact["slot"];
40 }) => Effect.Effect<import("effect/Option").Option<AuthArtifact>, Error, never>;40 }) => Effect.Effect<import("effect/Option").Option<AuthArtifact>, Error, never>;
41 upsert: (artifact: AuthArtifact) => Effect.Effect<void, Error, never>;41 upsert: (artifact: AuthArtifact) => Effect.Effect<void, Error, never>;
42 removeByWorkspaceSourceAndActor: (input: {42 removeByScopeSourceAndActor: (input: {
43 workspaceId: AuthArtifact["workspaceId"];43 scopeId: AuthArtifact["scopeId"];
44 sourceId: AuthArtifact["sourceId"];44 sourceId: AuthArtifact["sourceId"];
45 actorAccountId: AuthArtifact["actorAccountId"];45 actorScopeId: AuthArtifact["actorScopeId"];
46 slot?: AuthArtifact["slot"];46 slot?: AuthArtifact["slot"];
47 }) => Effect.Effect<boolean, Error, never>;47 }) => Effect.Effect<boolean, Error, never>;
48 removeByWorkspaceAndSourceId: (input: {48 removeByScopeAndSourceId: (input: {
49 workspaceId: AuthArtifact["workspaceId"];49 scopeId: AuthArtifact["scopeId"];
50 sourceId: AuthArtifact["sourceId"];50 sourceId: AuthArtifact["sourceId"];
51 }) => Effect.Effect<number, Error, never>;51 }) => Effect.Effect<number, Error, never>;
52 };52 };
@@ -61,41 +61,41 @@ export type ExecutorStateStoreShape = { · 8 unmodified lines
61 ) => Effect.Effect<boolean, Error, never>;61 ) => Effect.Effect<boolean, Error, never>;
62 };62 };
63 sourceOauthClients: {63 sourceOauthClients: {
64 getByWorkspaceSourceAndProvider: (input: {64 getByScopeSourceAndProvider: (input: {
65 workspaceId: WorkspaceSourceOauthClient["workspaceId"];65 scopeId: ScopedSourceOauthClient["scopeId"];
66 sourceId: WorkspaceSourceOauthClient["sourceId"];66 sourceId: ScopedSourceOauthClient["sourceId"];
67 providerKey: string;67 providerKey: string;
68 }) => Effect.Effect<68 }) => Effect.Effect<
69 import("effect/Option").Option<WorkspaceSourceOauthClient>,69 import("effect/Option").Option<ScopedSourceOauthClient>,
70 Error,70 Error,
71 never71 never
72 >;72 >;
73 upsert: (73 upsert: (
74 oauthClient: WorkspaceSourceOauthClient,74 oauthClient: ScopedSourceOauthClient,
75 ) => Effect.Effect<void, Error, never>;75 ) => Effect.Effect<void, Error, never>;
76 removeByWorkspaceAndSourceId: (input: {76 removeByScopeAndSourceId: (input: {
77 workspaceId: WorkspaceSourceOauthClient["workspaceId"];77 scopeId: ScopedSourceOauthClient["scopeId"];
78 sourceId: WorkspaceSourceOauthClient["sourceId"];78 sourceId: ScopedSourceOauthClient["sourceId"];
79 }) => Effect.Effect<number, Error, never>;79 }) => Effect.Effect<number, Error, never>;
80 };80 };
81 workspaceOauthClients: {81 scopeOauthClients: {
82 listByWorkspaceAndProvider: (input: {82 listByScopeAndProvider: (input: {
83 workspaceId: WorkspaceOauthClient["workspaceId"];83 scopeId: ScopeOauthClient["scopeId"];
84 providerKey: string;84 providerKey: string;
85 }) => Effect.Effect<readonly WorkspaceOauthClient[], Error, never>;85 }) => Effect.Effect<readonly ScopeOauthClient[], Error, never>;
86 getById: (86 getById: (
87 id: WorkspaceOauthClient["id"],87 id: ScopeOauthClient["id"],
88 ) => Effect.Effect<import("effect/Option").Option<WorkspaceOauthClient>, Error, never>;88 ) => Effect.Effect<import("effect/Option").Option<ScopeOauthClient>, Error, never>;
89 upsert: (oauthClient: WorkspaceOauthClient) => Effect.Effect<void, Error, never>;89 upsert: (oauthClient: ScopeOauthClient) => Effect.Effect<void, Error, never>;
90 removeById: (id: WorkspaceOauthClient["id"]) => Effect.Effect<boolean, Error, never>;90 removeById: (id: ScopeOauthClient["id"]) => Effect.Effect<boolean, Error, never>;
91 };91 };
92 providerAuthGrants: {92 providerAuthGrants: {
93 listByWorkspaceId: (93 listByScopeId: (
94 workspaceId: ProviderAuthGrant["workspaceId"],94 scopeId: ProviderAuthGrant["scopeId"],
95 ) => Effect.Effect<readonly ProviderAuthGrant[], Error, never>;95 ) => Effect.Effect<readonly ProviderAuthGrant[], Error, never>;
96 listByWorkspaceActorAndProvider: (input: {96 listByScopeActorAndProvider: (input: {
97 workspaceId: ProviderAuthGrant["workspaceId"];97 scopeId: ProviderAuthGrant["scopeId"];
98 actorAccountId: ProviderAuthGrant["actorAccountId"];98 actorScopeId: ProviderAuthGrant["actorScopeId"];
99 providerKey: string;99 providerKey: string;
100 }) => Effect.Effect<readonly ProviderAuthGrant[], Error, never>;100 }) => Effect.Effect<readonly ProviderAuthGrant[], Error, never>;
101 getById: (101 getById: (
@@ -106,8 +106,8 @@ export type ExecutorStateStoreShape = { · 4 unmodified lines
106 };106 };
107 sourceAuthSessions: {107 sourceAuthSessions: {
108 listAll: () => Effect.Effect<readonly SourceAuthSession[], Error, never>;108 listAll: () => Effect.Effect<readonly SourceAuthSession[], Error, never>;
109 listByWorkspaceId: (109 listByScopeId: (
110 workspaceId: SourceAuthSession["workspaceId"],110 scopeId: SourceAuthSession["scopeId"],
111 ) => Effect.Effect<readonly SourceAuthSession[], Error, never>;111 ) => Effect.Effect<readonly SourceAuthSession[], Error, never>;
112 getById: (112 getById: (
113 id: SourceAuthSession["id"],113 id: SourceAuthSession["id"],
@@ -115,10 +115,10 @@ export type ExecutorStateStoreShape = { · 1 unmodified line
115 getByState: (115 getByState: (
116 state: SourceAuthSession["state"],116 state: SourceAuthSession["state"],
117 ) => Effect.Effect<import("effect/Option").Option<SourceAuthSession>, Error, never>;117 ) => Effect.Effect<import("effect/Option").Option<SourceAuthSession>, Error, never>;
118 getPendingByWorkspaceSourceAndActor: (input: {118 getPendingByScopeSourceAndActor: (input: {
119 workspaceId: SourceAuthSession["workspaceId"];119 scopeId: SourceAuthSession["scopeId"];
120 sourceId: SourceAuthSession["sourceId"];120 sourceId: SourceAuthSession["sourceId"];
121 actorAccountId: SourceAuthSession["actorAccountId"];121 actorScopeId: SourceAuthSession["actorScopeId"];
122 credentialSlot?: SourceAuthSession["credentialSlot"];122 credentialSlot?: SourceAuthSession["credentialSlot"];
123 }) => Effect.Effect<123 }) => Effect.Effect<
124 import("effect/Option").Option<SourceAuthSession>,124 import("effect/Option").Option<SourceAuthSession>,
@@ -129,12 +129,12 @@ export type ExecutorStateStoreShape = { · 4 unmodified lines
129 update: (129 update: (
130 id: SourceAuthSession["id"],130 id: SourceAuthSession["id"],
131 patch: Partial<131 patch: Partial<
132 Omit<SourceAuthSession, "id" | "workspaceId" | "sourceId" | "createdAt">132 Omit<SourceAuthSession, "id" | "scopeId" | "sourceId" | "createdAt">
133 >,133 >,
134 ) => Effect.Effect<import("effect/Option").Option<SourceAuthSession>, Error, never>;134 ) => Effect.Effect<import("effect/Option").Option<SourceAuthSession>, Error, never>;
135 upsert: (session: SourceAuthSession) => Effect.Effect<void, Error, never>;135 upsert: (session: SourceAuthSession) => Effect.Effect<void, Error, never>;
136 removeByWorkspaceAndSourceId: (136 removeByScopeAndSourceId: (
137 workspaceId: SourceAuthSession["workspaceId"],137 scopeId: SourceAuthSession["scopeId"],
138 sourceId: SourceAuthSession["sourceId"],138 sourceId: SourceAuthSession["sourceId"],
139 ) => Effect.Effect<boolean, Error, never>;139 ) => Effect.Effect<boolean, Error, never>;
140 };140 };
@@ -158,15 +158,15 @@ export type ExecutorStateStoreShape = { · 17 unmodified lines
158 getById: (158 getById: (
159 executionId: Execution["id"],159 executionId: Execution["id"],
160 ) => Effect.Effect<import("effect/Option").Option<Execution>, Error, never>;160 ) => Effect.Effect<import("effect/Option").Option<Execution>, Error, never>;
161 getByWorkspaceAndId: (161 getByScopeAndId: (
162 workspaceId: Execution["workspaceId"],162 scopeId: Execution["scopeId"],
163 executionId: Execution["id"],163 executionId: Execution["id"],
164 ) => Effect.Effect<import("effect/Option").Option<Execution>, Error, never>;164 ) => Effect.Effect<import("effect/Option").Option<Execution>, Error, never>;
165 insert: (execution: Execution) => Effect.Effect<void, Error, never>;165 insert: (execution: Execution) => Effect.Effect<void, Error, never>;
166 update: (166 update: (
167 executionId: Execution["id"],167 executionId: Execution["id"],
168 patch: Partial<168 patch: Partial<
169 Omit<Execution, "id" | "workspaceId" | "createdByAccountId" | "createdAt">169 Omit<Execution, "id" | "scopeId" | "createdByScopeId" | "createdAt">
170 >,170 >,
171 ) => Effect.Effect<import("effect/Option").Option<Execution>, Error, never>;171 ) => Effect.Effect<import("effect/Option").Option<Execution>, Error, never>;
172 };172 };
packages/platform/sdk/src/runtime/index.ts+110−72
@@ -3,30 +3,56 @@ import { · 2 unmodified lines
3 makeToolInvokerFromTools,3 makeToolInvokerFromTools,
4 type ToolMap,4 type ToolMap,
5} from "@executor/codemode-core";5} from "@executor/codemode-core";
6import { clearAllMcpConnectionPools } from "@executor/source-mcp";6import {
7import type { LocalInstallation } from "#schema";7 clearAllMcpConnectionPools,
8} from "@executor/source-mcp";
9import type {
10 LocalInstallation,
11} from "#schema";
8import * as Effect from "effect/Effect";12import * as Effect from "effect/Effect";
9import * as Layer from "effect/Layer";13import * as Layer from "effect/Layer";
10import * as ManagedRuntime from "effect/ManagedRuntime";14import * as ManagedRuntime from "effect/ManagedRuntime";
11 15
12import { RuntimeSourceAuthMaterialLive } from "./auth/source-auth-material";16import {
13import { RuntimeSourceCatalogStoreLive } from "./catalog/source/runtime";17 RuntimeSourceAuthMaterialLive,
14import { reconcileMissingSourceCatalogArtifacts } from "./catalog/source/reconcile";18} from "./auth/source-auth-material";
15import { RuntimeSourceCatalogSyncLive } from "./catalog/source/sync";19import {
20 RuntimeSourceCatalogStoreLive,
21} from "./catalog/source/runtime";
22import {
23 reconcileMissingSourceCatalogArtifacts,
24} from "./catalog/source/reconcile";
25import {
26 RuntimeSourceCatalogSyncLive,
27} from "./catalog/source/sync";
16import {28import {
17 SourceTypeDeclarationsRefresherService,29 SourceTypeDeclarationsRefresherService,
18 type SourceTypeDeclarationsRefresherShape,30 type SourceTypeDeclarationsRefresherShape,
19} from "./catalog/source/type-declarations";31} from "./catalog/source/type-declarations";
20import { createLiveExecutionManager, LiveExecutionManagerService } from "./execution/live";32import {
21import { RuntimeExecutionResolverLive } from "./execution/workspace/environment";33 createLiveExecutionManager,
22import type { CreateWorkspaceInternalToolMap, WorkspaceInternalToolContext } from "./execution/workspace/tool-invoker";34 LiveExecutionManagerService,
23import type { LoadedLocalExecutorConfig } from "./workspace-config";35} from "./execution/live";
24import type { LocalExecutorConfig } from "#schema";36import {
25import type { InstanceConfig } from "../local/contracts";37 RuntimeExecutionResolverLive,
38} from "./execution/scope/environment";
39import type {
40 CreateScopeInternalToolMap,
41 ScopeInternalToolContext,
42} from "./execution/scope/tool-invoker";
43import type {
44 LoadedLocalExecutorConfig,
45} from "./scope-config";
26import type {46import type {
27 ExecutorWorkspaceContext,47 LocalExecutorConfig,
28 ExecutorWorkspaceDescriptor,48} from "#schema";
29} from "../workspace";49import type {
50 InstanceConfig,
51} from "../local/contracts";
52import type {
53 ExecutorScopeContext,
54 ExecutorScopeDescriptor,
55} from "../scope";
30import {56import {
31 LocalInstanceConfigService,57 LocalInstanceConfigService,
32 type DeleteSecretMaterial,58 type DeleteSecretMaterial,
@@ -37,12 +63,14 @@ import { · 4 unmodified lines
37 SecretMaterialUpdaterService,63 SecretMaterialUpdaterService,
38 type StoreSecretMaterial,64 type StoreSecretMaterial,
39 type UpdateSecretMaterial,65 type UpdateSecretMaterial,
40} from "./workspace/secret-material-providers";66} from "./scope/secret-material-providers";
41import type { LocalSourceArtifact } from "./source-artifacts";67import type {
68 LocalSourceArtifact,
69} from "./source-artifacts";
42import {70import {
43 type RuntimeLocalWorkspaceState,71 type RuntimeLocalScopeState,
44 RuntimeLocalWorkspaceLive,72 RuntimeLocalScopeLive,
45} from "./workspace/runtime-context";73} from "./scope/runtime-context";
46import {74import {
47 type LocalToolRuntime,75 type LocalToolRuntime,
48 type LocalToolRuntimeLoaderShape,76 type LocalToolRuntimeLoaderShape,
@@ -53,14 +81,25 @@ import { · 4 unmodified lines
53 makeLocalStorageLayer,81 makeLocalStorageLayer,
54 type InstallationStoreShape,82 type InstallationStoreShape,
55 type SourceArtifactStoreShape,83 type SourceArtifactStoreShape,
56 type WorkspaceConfigStoreShape,84 type ScopeConfigStoreShape,
57 type WorkspaceStateStoreShape,85 type ScopeStateStoreShape,
58} from "./workspace/storage";86} from "./scope/storage";
59import type { LocalWorkspaceState } from "./workspace-state";87import type {
60import { synchronizeLocalWorkspaceState } from "./workspace/workspace-sync";88 LocalScopeState,
61import { ExecutorStateStore, type ExecutorStateStoreShape } from "./executor-state-store";89} from "./scope-state";
62import { RuntimeSourceAuthServiceLive } from "./sources/source-auth-service";90import {
63import { RuntimeSourceStoreLive } from "./sources/source-store";91 synchronizeLocalScopeState,
92} from "./scope/scope-sync";
93import {
94 ExecutorStateStore,
95 type ExecutorStateStoreShape,
96} from "./executor-state-store";
97import {
98 RuntimeSourceAuthServiceLive,
99} from "./sources/source-auth-service";
100import {
101 RuntimeSourceStoreLive,
102} from "./sources/source-store";
64 103
65export * from "./execution/state";104export * from "./execution/state";
66export * from "./sources/executor-tools";105export * from "./sources/executor-tools";
@@ -73,28 +112,28 @@ export * from "./sources/source-credential-interactions"; · 6 unmodified lines
73export * from "./sources/source-adapters/mcp";112export * from "./sources/source-adapters/mcp";
74export * from "./sources/source-store";113export * from "./sources/source-store";
75export * from "./executor-state-store";114export * from "./executor-state-store";
76export * from "./execution/workspace/environment";115export * from "./execution/scope/environment";
77export * from "../sources/inspection";116export * from "../sources/inspection";
78export * from "../sources/discovery";117export * from "../sources/discovery";
79export * from "./execution/service";118export * from "./execution/service";
80export type {119export type {
81 CreateWorkspaceInternalToolMap,120 CreateScopeInternalToolMap,
82 WorkspaceInternalToolContext,121 ScopeInternalToolContext,
83} from "./execution/workspace/tool-invoker";122} from "./execution/scope/tool-invoker";
84export {123export {
85 LocalInstanceConfigService,124 LocalInstanceConfigService,
86 SecretMaterialDeleterService,125 SecretMaterialDeleterService,
87 SecretMaterialResolverService,126 SecretMaterialResolverService,
88 SecretMaterialStorerService,127 SecretMaterialStorerService,
89 SecretMaterialUpdaterService,128 SecretMaterialUpdaterService,
90} from "./workspace/secret-material-providers";129} from "./scope/secret-material-providers";
91export type {130export type {
92 DeleteSecretMaterial,131 DeleteSecretMaterial,
93 ResolveInstanceConfig,132 ResolveInstanceConfig,
94 ResolveSecretMaterial,133 ResolveSecretMaterial,
95 StoreSecretMaterial,134 StoreSecretMaterial,
96 UpdateSecretMaterial,135 UpdateSecretMaterial,
97} from "./workspace/secret-material-providers";136} from "./scope/secret-material-providers";
98export {137export {
99 builtInSourceAdapters,138 builtInSourceAdapters,
100 connectableSourceAdapters,139 connectableSourceAdapters,
@@ -112,7 +151,7 @@ export { · 11 unmodified lines
112 151
113export type ExecutorRuntimeOptions = {152export type ExecutorRuntimeOptions = {
114 executionResolver?: ResolveExecutionEnvironment;153 executionResolver?: ResolveExecutionEnvironment;
115 createInternalToolMap?: CreateWorkspaceInternalToolMap;154 createInternalToolMap?: CreateScopeInternalToolMap;
116 resolveSecretMaterial?: ResolveSecretMaterial;155 resolveSecretMaterial?: ResolveSecretMaterial;
117 getLocalServerBaseUrl?: () => string | undefined;156 getLocalServerBaseUrl?: () => string | undefined;
118};157};
@@ -134,18 +173,18 @@ export type BoundInstallationStore = { · 15 unmodified lines
134 getOrProvision: () => Effect.Effect<LocalInstallation, Error, never>;173 getOrProvision: () => Effect.Effect<LocalInstallation, Error, never>;
135};174};
136 175
137export type BoundWorkspaceConfigStore = {176export type BoundScopeConfigStore = {
138 load: () => Effect.Effect<LoadedLocalExecutorConfig, Error, never>;177 load: () => Effect.Effect<LoadedLocalExecutorConfig, Error, never>;
139 writeProject: (178 writeProject: (
140 config: LocalExecutorConfig,179 config: LocalExecutorConfig,
141 ) => Effect.Effect<void, Error, never>;180 ) => Effect.Effect<void, Error, never>;
142 resolveRelativePath: WorkspaceConfigStoreShape["resolveRelativePath"];181 resolveRelativePath: ScopeConfigStoreShape["resolveRelativePath"];
143};182};
144 183
145export type BoundWorkspaceStateStore = {184export type BoundScopeStateStore = {
146 load: () => Effect.Effect<LocalWorkspaceState, Error, never>;185 load: () => Effect.Effect<LocalScopeState, Error, never>;
147 write: (186 write: (
148 state: LocalWorkspaceState,187 state: LocalScopeState,
149 ) => Effect.Effect<void, Error, never>;188 ) => Effect.Effect<void, Error, never>;
150};189};
151 190
@@ -181,8 +220,8 @@ export type RuntimeInstanceConfigService = { · 29 unmodified lines
181 220
182export type RuntimeStorageServices = {221export type RuntimeStorageServices = {
183 installation: BoundInstallationStore;222 installation: BoundInstallationStore;
184 workspaceConfig: BoundWorkspaceConfigStore;223 scopeConfig: BoundScopeConfigStore;
185 workspaceState: BoundWorkspaceStateStore;224 scopeState: BoundScopeStateStore;
186 sourceArtifacts: BoundSourceArtifactStore;225 sourceArtifacts: BoundSourceArtifactStore;
187 executorState: ExecutorStateStoreShape;226 executorState: ExecutorStateStoreShape;
188 secretMaterial: RuntimeSecretMaterialServices;227 secretMaterial: RuntimeSecretMaterialServices;
@@ -190,7 +229,7 @@ export type RuntimeStorageServices = { · 1 unmodified line
190};229};
191 230
192export type ExecutorRuntimeServices = {231export type ExecutorRuntimeServices = {
193 workspace: ExecutorWorkspaceDescriptor;232 scope: ExecutorScopeDescriptor;
194 storage: RuntimeStorageServices;233 storage: RuntimeStorageServices;
195 localToolRuntimeLoader?: BoundLocalToolRuntimeLoader;234 localToolRuntimeLoader?: BoundLocalToolRuntimeLoader;
196 sourceTypeDeclarationsRefresher?: BoundSourceTypeDeclarationsRefresher;235 sourceTypeDeclarationsRefresher?: BoundSourceTypeDeclarationsRefresher;
@@ -219,17 +258,17 @@ const toInstallationStoreShape = ( · 22 unmodified lines
219 getOrProvision: input.getOrProvision,258 getOrProvision: input.getOrProvision,
220});259});
221 260
222const toWorkspaceConfigStoreShape = (261const toScopeConfigStoreShape = (
223 input: BoundWorkspaceConfigStore,262 input: BoundScopeConfigStore,
224): WorkspaceConfigStoreShape => ({263): ScopeConfigStoreShape => ({
225 load: input.load,264 load: input.load,
226 writeProject: ({ config }) => input.writeProject(config),265 writeProject: ({ config }) => input.writeProject(config),
227 resolveRelativePath: input.resolveRelativePath,266 resolveRelativePath: input.resolveRelativePath,
228});267});
229 268
230const toWorkspaceStateStoreShape = (269const toScopeStateStoreShape = (
231 input: BoundWorkspaceStateStore,270 input: BoundScopeStateStore,
232): WorkspaceStateStoreShape => ({271): ScopeStateStoreShape => ({
233 load: input.load,272 load: input.load,
234 write: ({ state }) => input.write(state),273 write: ({ state }) => input.write(state),
235});274});
@@ -256,14 +295,14 @@ const makeInstanceConfigLayer = (input: RuntimeInstanceConfigService) => · 20 unmodified lines
256 295
257export const createExecutorRuntimeLayer = (296export const createExecutorRuntimeLayer = (
258 input: ExecutorRuntimeOptions & ExecutorRuntimeServices & {297 input: ExecutorRuntimeOptions & ExecutorRuntimeServices & {
259 localWorkspaceState: RuntimeLocalWorkspaceState;298 localScopeState: RuntimeLocalScopeState;
260 liveExecutionManager: ReturnType<typeof createLiveExecutionManager>;299 liveExecutionManager: ReturnType<typeof createLiveExecutionManager>;
261 },300 },
262) => {301) => {
263 const storageLayer = makeLocalStorageLayer({302 const storageLayer = makeLocalStorageLayer({
264 installationStore: toInstallationStoreShape(input.storage.installation),303 installationStore: toInstallationStoreShape(input.storage.installation),
265 workspaceConfigStore: toWorkspaceConfigStoreShape(input.storage.workspaceConfig),304 scopeConfigStore: toScopeConfigStoreShape(input.storage.scopeConfig),
266 workspaceStateStore: toWorkspaceStateStoreShape(input.storage.workspaceState),305 scopeStateStore: toScopeStateStoreShape(input.storage.scopeState),
267 sourceArtifactStore: toSourceArtifactStoreShape(input.storage.sourceArtifacts),306 sourceArtifactStore: toSourceArtifactStoreShape(input.storage.sourceArtifacts),
268 });307 });
269 const localToolRuntimeLayer = Layer.succeed(308 const localToolRuntimeLayer = Layer.succeed(
@@ -283,7 +322,7 @@ export const createExecutorRuntimeLayer = ( · 13 unmodified lines
283 322
284 const baseLayer = Layer.mergeAll(323 const baseLayer = Layer.mergeAll(
285 Layer.succeed(ExecutorStateStore, input.storage.executorState),324 Layer.succeed(ExecutorStateStore, input.storage.executorState),
286 RuntimeLocalWorkspaceLive(input.localWorkspaceState),325 RuntimeLocalScopeLive(input.localScopeState),
287 storageLayer,326 storageLayer,
288 Layer.succeed(LiveExecutionManagerService, input.liveExecutionManager),327 Layer.succeed(LiveExecutionManagerService, input.liveExecutionManager),
289 sourceTypeDeclarationsRefresherLayer,328 sourceTypeDeclarationsRefresherLayer,
@@ -381,16 +420,18 @@ export const createExecutorRuntimeFromServices = (input: { · 91 unmodified lines
381 .getOrProvision()420 .getOrProvision()
382 .pipe(Effect.mapError(toRuntimeBootstrapError));421 .pipe(Effect.mapError(toRuntimeBootstrapError));
383 422
384 const loadedLocalConfig = yield* input.services.storage.workspaceConfig423 const loadedLocalConfig = yield* input.services.storage.scopeConfig
385 .load()424 .load()
386 .pipe(Effect.mapError(toRuntimeBootstrapError));425 .pipe(Effect.mapError(toRuntimeBootstrapError));
387 426
388 const runtimeWorkspace: ExecutorWorkspaceContext = {427 const runtimeWorkspace: ExecutorScopeContext = {
389 ...input.services.workspace,428 ...input.services.scope,
390 workspaceId: localInstallation.workspaceId,429 scopeId: localInstallation.scopeId,
391 accountId: localInstallation.accountId,430 actorScopeId: input.services.scope.actorScopeId ?? localInstallation.actorScopeId,
431 resolutionScopeIds:
432 input.services.scope.resolutionScopeIds ?? localInstallation.resolutionScopeIds,
392 };433 };
393 const effectiveLocalConfig = yield* synchronizeLocalWorkspaceState({434 const effectiveLocalConfig = yield* synchronizeLocalScopeState({
394 loadedConfig: loadedLocalConfig,435 loadedConfig: loadedLocalConfig,
395 })436 })
396 .pipe(437 .pipe(
@@ -399,11 +440,11 @@ export const createExecutorRuntimeFromServices = (input: { · 2 unmodified lines
399 installationStore: toInstallationStoreShape(440 installationStore: toInstallationStoreShape(
400 input.services.storage.installation,441 input.services.storage.installation,
401 ),442 ),
402 workspaceConfigStore: toWorkspaceConfigStoreShape(443 scopeConfigStore: toScopeConfigStoreShape(
403 input.services.storage.workspaceConfig,444 input.services.storage.scopeConfig,
404 ),445 ),
405 workspaceStateStore: toWorkspaceStateStoreShape(446 scopeStateStore: toScopeStateStoreShape(
406 input.services.storage.workspaceState,447 input.services.storage.scopeState,
407 ),448 ),
408 sourceArtifactStore: toSourceArtifactStoreShape(449 sourceArtifactStore: toSourceArtifactStoreShape(
409 input.services.storage.sourceArtifacts,450 input.services.storage.sourceArtifacts,
@@ -412,12 +453,9 @@ export const createExecutorRuntimeFromServices = (input: { · 2 unmodified lines
412 ),453 ),
413 )454 )
414 .pipe(Effect.mapError(toRuntimeBootstrapError));455 .pipe(Effect.mapError(toRuntimeBootstrapError));
415 const runtimeLocalWorkspaceState: RuntimeLocalWorkspaceState = {456 const runtimeLocalScopeState: RuntimeLocalScopeState = {
416 workspace: runtimeWorkspace,457 scope: runtimeWorkspace,
417 installation: {458 installation: localInstallation,
418 workspaceId: localInstallation.workspaceId,
419 accountId: localInstallation.accountId,
420 },
421 loadedConfig: {459 loadedConfig: {
422 ...loadedLocalConfig,460 ...loadedLocalConfig,
423 config: effectiveLocalConfig,461 config: effectiveLocalConfig,
@@ -428,14 +466,14 @@ export const createExecutorRuntimeFromServices = (input: { · 4 unmodified lines
428 const concreteRuntimeLayer = createExecutorRuntimeLayer({466 const concreteRuntimeLayer = createExecutorRuntimeLayer({
429 ...input,467 ...input,
430 ...input.services,468 ...input.services,
431 localWorkspaceState: runtimeLocalWorkspaceState,469 localScopeState: runtimeLocalScopeState,
432 liveExecutionManager,470 liveExecutionManager,
433 });471 });
434 const managedRuntime = ManagedRuntime.make(concreteRuntimeLayer);472 const managedRuntime = ManagedRuntime.make(concreteRuntimeLayer);
435 yield* managedRuntime.runtimeEffect;473 yield* managedRuntime.runtimeEffect;
436 yield* reconcileMissingSourceCatalogArtifacts({474 yield* reconcileMissingSourceCatalogArtifacts({
437 workspaceId: localInstallation.workspaceId,475 scopeId: localInstallation.scopeId,
438 actorAccountId: localInstallation.accountId,476 actorScopeId: localInstallation.actorScopeId,
439 }).pipe(477 }).pipe(
440 Effect.provide(managedRuntime),478 Effect.provide(managedRuntime),
441 Effect.catchAll(() => Effect.void),479 Effect.catchAll(() => Effect.void),
packages/platform/sdk/src/runtime/policy/invocation-policy-engine.test.ts+18−12
@@ -1,10 +1,16 @@
1import { describe, expect, it } from "@effect/vitest";1import {
2 describe,
3 expect,
4 it,
5} from "@effect/vitest";
2 6
3import type { LocalWorkspacePolicy } from "#schema";7import type {
8 LocalScopePolicy,
9} from "#schema";
4import {10import {
5 PolicyIdSchema,11 PolicyIdSchema,
6 SourceIdSchema,12 SourceIdSchema,
7 WorkspaceIdSchema,13 ScopeIdSchema,
8} from "#schema";14} from "#schema";
9 15
10import {16import {
@@ -12,7 +18,7 @@ import { · 1 unmodified line
12 type InvocationDescriptor,18 type InvocationDescriptor,
13} from "./invocation-policy-engine";19} from "./invocation-policy-engine";
14 20
15const workspaceId = WorkspaceIdSchema.make("ws_policy_engine");21const scopeId = ScopeIdSchema.make("ws_policy_engine");
16const sourceId = SourceIdSchema.make("src_policy_engine");22const sourceId = SourceIdSchema.make("src_policy_engine");
17 23
18const now = 1_700_000_000_000;24const now = 1_700_000_000_000;
@@ -29,11 +35,11 @@ const baseDescriptor: InvocationDescriptor = { · 10 unmodified lines
29};35};
30 36
31const basePolicy = (37const basePolicy = (
32 patch: Partial<LocalWorkspacePolicy> = {},38 patch: Partial<LocalScopePolicy> = {},
33): LocalWorkspacePolicy => ({39): LocalScopePolicy => ({
34 id: PolicyIdSchema.make(`pol_${Math.random().toString(36).slice(2, 8)}`),40 id: PolicyIdSchema.make(`pol_${Math.random().toString(36).slice(2, 8)}`),
35 key: "vercel-dns",41 key: "vercel-dns",
36 workspaceId,42 scopeId,
37 resourcePattern: "vercel.api.dns.createRecord",43 resourcePattern: "vercel.api.dns.createRecord",
38 effect: "allow",44 effect: "allow",
39 approvalMode: "auto",45 approvalMode: "auto",
@@ -57,7 +63,7 @@ describe("invocation-policy-engine", () => { · 17 unmodified lines
57 args: {},63 args: {},
58 policies: [],64 policies: [],
59 context: {65 context: {
60 workspaceId,66 scopeId,
61 },67 },
62 });68 });
63 69
@@ -70,7 +76,7 @@ describe("invocation-policy-engine", () => { · 6 unmodified lines
70 args: { domain: "testing.executor.sh" },76 args: { domain: "testing.executor.sh" },
71 policies: [],77 policies: [],
72 context: {78 context: {
73 workspaceId,79 scopeId,
74 },80 },
75 });81 });
76 82
@@ -85,7 +91,7 @@ describe("invocation-policy-engine", () => { · 8 unmodified lines
85 id: PolicyIdSchema.make("pol_allow"),91 id: PolicyIdSchema.make("pol_allow"),
86 })],92 })],
87 context: {93 context: {
88 workspaceId,94 scopeId,
89 },95 },
90 });96 });
91 97
@@ -113,7 +119,7 @@ describe("invocation-policy-engine", () => { · 21 unmodified lines
113 }),119 }),
114 ],120 ],
115 context: {121 context: {
116 workspaceId,122 scopeId,
117 },123 },
118 });124 });
119 125
@@ -130,7 +136,7 @@ describe("invocation-policy-engine", () => { · 10 unmodified lines
130 enabled: false,136 enabled: false,
131 })],137 })],
132 context: {138 context: {
133 workspaceId,139 scopeId,
134 },140 },
135 });141 });
136 142
packages/platform/sdk/src/runtime/policy/invocation-policy-engine.ts+7−7
@@ -1,5 +1,5 @@
1import type {1import type {
2 LocalWorkspacePolicy,2 LocalScopePolicy,
3 Source,3 Source,
4} from "#schema";4} from "#schema";
5 5
@@ -22,13 +22,13 @@ export type InvocationDescriptor = { · 16 unmodified lines
22};22};
23 23
24export type InvocationPolicyContext = {24export type InvocationPolicyContext = {
25 workspaceId: LocalWorkspacePolicy["workspaceId"];25 scopeId: LocalScopePolicy["scopeId"];
26};26};
27 27
28export type InvocationAuthorizationDecision = {28export type InvocationAuthorizationDecision = {
29 kind: "allow" | "deny" | "require_interaction";29 kind: "allow" | "deny" | "require_interaction";
30 reason: string;30 reason: string;
31 matchedPolicyId: LocalWorkspacePolicy["id"] | null;31 matchedPolicyId: LocalScopePolicy["id"] | null;
32};32};
33 33
34const matchesGlob = (pattern: string, value: string): boolean => {34const matchesGlob = (pattern: string, value: string): boolean => {
@@ -38,7 +38,7 @@ const matchesGlob = (pattern: string, value: string): boolean => { · 3 unmodified lines
38 return new RegExp(`^${escaped}$`).test(value);38 return new RegExp(`^${escaped}$`).test(value);
39};39};
40 40
41const policySpecificity = (policy: LocalWorkspacePolicy): number =>41const policySpecificity = (policy: LocalScopePolicy): number =>
42 policy.priority + Math.max(1, policy.resourcePattern.replace(/\*/g, "").length);42 policy.priority + Math.max(1, policy.resourcePattern.replace(/\*/g, "").length);
43 43
44const defaultDecisionForInvocation = (44const defaultDecisionForInvocation = (
@@ -60,7 +60,7 @@ const defaultDecisionForInvocation = ( · 15 unmodified lines
60};60};
61 61
62const resolvePolicyDecision = (62const resolvePolicyDecision = (
63 policy: LocalWorkspacePolicy,63 policy: LocalScopePolicy,
64): InvocationAuthorizationDecision => {64): InvocationAuthorizationDecision => {
65 if (policy.effect === "deny") {65 if (policy.effect === "deny") {
66 return {66 return {
@@ -88,13 +88,13 @@ const resolvePolicyDecision = ( · 21 unmodified lines
88export const evaluateInvocationPolicy = (input: {88export const evaluateInvocationPolicy = (input: {
89 descriptor: InvocationDescriptor;89 descriptor: InvocationDescriptor;
90 args: unknown;90 args: unknown;
91 policies: ReadonlyArray<LocalWorkspacePolicy>;91 policies: ReadonlyArray<LocalScopePolicy>;
92 context: InvocationPolicyContext;92 context: InvocationPolicyContext;
93}): InvocationAuthorizationDecision => {93}): InvocationAuthorizationDecision => {
94 const matchingPolicies = input.policies94 const matchingPolicies = input.policies
95 .filter((policy) =>95 .filter((policy) =>
96 policy.enabled96 policy.enabled
97 && policy.workspaceId === input.context.workspaceId97 && policy.scopeId === input.context.scopeId
98 && matchesGlob(policy.resourcePattern, input.descriptor.toolPath))98 && matchesGlob(policy.resourcePattern, input.descriptor.toolPath))
99 .sort((left, right) =>99 .sort((left, right) =>
100 policySpecificity(right) - policySpecificity(left)100 policySpecificity(right) - policySpecificity(left)
packages/platform/sdk/src/runtime/scope-config.tsrenamed from packages/platform/sdk/src/runtime/workspace-config.ts+3−1
@@ -1,4 +1,6 @@
1import type { LocalExecutorConfig } from "#schema";1import type {
2 LocalExecutorConfig,
3} from "#schema";
2 4
3export type LoadedExecutorConfig = {5export type LoadedExecutorConfig = {
4 config: LocalExecutorConfig | null;6 config: LocalExecutorConfig | null;
packages/platform/sdk/src/runtime/scope-errors.tsrenamed from packages/platform/sdk/src/runtime/workspace-errors.ts+6−6
@@ -1,17 +1,17 @@
1import * as Data from "effect/Data";1import * as Data from "effect/Data";
2 2
3export class RuntimeLocalWorkspaceUnavailableError extends Data.TaggedError(3export class RuntimeLocalScopeUnavailableError extends Data.TaggedError(
4 "RuntimeLocalWorkspaceUnavailableError",4 "RuntimeLocalScopeUnavailableError",
5)<{5)<{
6 readonly message: string;6 readonly message: string;
7}> {}7}> {}
8 8
9export class RuntimeLocalWorkspaceMismatchError extends Data.TaggedError(9export class RuntimeLocalScopeMismatchError extends Data.TaggedError(
10 "RuntimeLocalWorkspaceMismatchError",10 "RuntimeLocalScopeMismatchError",
11)<{11)<{
12 readonly message: string;12 readonly message: string;
13 readonly requestedWorkspaceId: string;13 readonly requestedScopeId: string;
14 readonly activeWorkspaceId: string;14 readonly activeScopeId: string;
15}> {}15}> {}
16 16
17export class LocalConfiguredSourceNotFoundError extends Data.TaggedError(17export class LocalConfiguredSourceNotFoundError extends Data.TaggedError(
packages/platform/sdk/src/runtime/scope-state.tsrenamed from packages/platform/sdk/src/runtime/workspace-state.ts+6−6
@@ -13,13 +13,13 @@ const LocalWorkspaceSourceStateSchema = Schema.Struct({ · 12 unmodified lines
13 updatedAt: TimestampMsSchema,13 updatedAt: TimestampMsSchema,
14});14});
15 15
16const LocalWorkspacePolicyStateSchema = Schema.Struct({16const LocalScopePolicyStateSchema = Schema.Struct({
17 id: PolicyIdSchema,17 id: PolicyIdSchema,
18 createdAt: TimestampMsSchema,18 createdAt: TimestampMsSchema,
19 updatedAt: TimestampMsSchema,19 updatedAt: TimestampMsSchema,
20});20});
21 21
22export const LocalWorkspaceStateSchema = Schema.Struct({22export const LocalScopeStateSchema = Schema.Struct({
23 version: Schema.Literal(1),23 version: Schema.Literal(1),
24 sources: Schema.Record({24 sources: Schema.Record({
25 key: Schema.String,25 key: Schema.String,
@@ -27,15 +27,15 @@ export const LocalWorkspaceStateSchema = Schema.Struct({ · 1 unmodified line
27 }),27 }),
28 policies: Schema.Record({28 policies: Schema.Record({
29 key: Schema.String,29 key: Schema.String,
30 value: LocalWorkspacePolicyStateSchema,30 value: LocalScopePolicyStateSchema,
31 }),31 }),
32});32});
33 33
34export type LocalWorkspaceSourceState = typeof LocalWorkspaceSourceStateSchema.Type;34export type LocalWorkspaceSourceState = typeof LocalWorkspaceSourceStateSchema.Type;
35export type LocalWorkspacePolicyState = typeof LocalWorkspacePolicyStateSchema.Type;35export type LocalScopePolicyState = typeof LocalScopePolicyStateSchema.Type;
36export type LocalWorkspaceState = typeof LocalWorkspaceStateSchema.Type;36export type LocalScopeState = typeof LocalScopeStateSchema.Type;
37 37
38export const defaultLocalWorkspaceState = (): LocalWorkspaceState => ({38export const defaultLocalScopeState = (): LocalScopeState => ({
39 version: 1,39 version: 1,
40 sources: {},40 sources: {},
41 policies: {},41 policies: {},
packages/platform/sdk/src/runtime/scope/config-secrets.tsrenamed from packages/platform/sdk/src/runtime/workspace/config-secrets.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/scope/runtime-context.ts+85−0
@@ -0,0 +1,85 @@
1import * as Context from "effect/Context";
2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4import * as Option from "effect/Option";
5
6import type {
7 ScopeId,
8} from "#schema";
9import type {
10 ExecutorScopeContext,
11} from "../../scope";
12import type {
13 LoadedLocalExecutorConfig,
14} from "../scope-config";
15import {
16 RuntimeLocalScopeMismatchError,
17 RuntimeLocalScopeUnavailableError,
18} from "../scope-errors";
19
20export type RuntimeLocalScopeState = {
21 scope: ExecutorScopeContext;
22 installation: {
23 scopeId: ScopeId;
24 actorScopeId: ScopeId;
25 resolutionScopeIds: ReadonlyArray<ScopeId>;
26 };
27 loadedConfig: LoadedLocalExecutorConfig;
28};
29
30export class RuntimeLocalScopeService extends Context.Tag(
31 "#runtime/RuntimeLocalScopeService",
32)<RuntimeLocalScopeService, RuntimeLocalScopeState>() {}
33
34export const RuntimeLocalScopeLive = (
35 runtimeLocalScope: RuntimeLocalScopeState,
36) => Layer.succeed(RuntimeLocalScopeService, runtimeLocalScope);
37
38export const provideOptionalRuntimeLocalScope = <A, E, R>(
39 effect: Effect.Effect<A, E, R>,
40 runtimeLocalScope: RuntimeLocalScopeState | null | undefined,
41): Effect.Effect<A, E, R> =>
42 runtimeLocalScope === null || runtimeLocalScope === undefined
43 ? effect
44 : effect.pipe(Effect.provide(RuntimeLocalScopeLive(runtimeLocalScope)));
45
46export const getRuntimeLocalScopeOption = () =>
47 Effect.contextWith((context) =>
48 Context.getOption(context, RuntimeLocalScopeService),
49 ).pipe(
50 Effect.map((option) => (Option.isSome(option) ? option.value : null)),
51 ) as Effect.Effect<RuntimeLocalScopeState | null, never, never>;
52
53export const requireRuntimeLocalScope = (
54 scopeId?: ScopeId,
55): Effect.Effect<
56 RuntimeLocalScopeState,
57 RuntimeLocalScopeUnavailableError | RuntimeLocalScopeMismatchError,
58 never
59> =>
60 Effect.gen(function* () {
61 const runtimeLocalScope = yield* getRuntimeLocalScopeOption();
62 if (runtimeLocalScope === null) {
63 return yield* new RuntimeLocalScopeUnavailableError({
64 message: "Runtime local scope is unavailable",
65 });
66 }
67
68 if (
69 scopeId !== undefined
70 && runtimeLocalScope.installation.scopeId !== scopeId
71 ) {
72 return yield* new RuntimeLocalScopeMismatchError({
73 message: `Scope ${scopeId} is not the active local scope ${runtimeLocalScope.installation.scopeId}`,
74 requestedScopeId: scopeId,
75 activeScopeId: runtimeLocalScope.installation.scopeId,
76 });
77 }
78
79 return runtimeLocalScope;
80 });
81
82export const requireRuntimeLocalActorScopeId = (scopeId?: ScopeId) =>
83 requireRuntimeLocalScope(scopeId).pipe(
84 Effect.map((runtimeLocalScope) => runtimeLocalScope.installation.actorScopeId),
85 );
packages/platform/sdk/src/runtime/scope/scope-sync.tsrenamed from packages/platform/sdk/src/runtime/workspace/workspace-sync.ts+16−10
@@ -1,8 +1,14 @@
1import * as Effect from "effect/Effect";1import * as Effect from "effect/Effect";
2 2
3import { type LoadedLocalExecutorConfig } from "../workspace-config";3import {
4import { WorkspaceStateStore } from "./storage";4 type LoadedLocalExecutorConfig,
5import { type LocalWorkspaceState } from "../workspace-state";5} from "../scope-config";
6import {
7 ScopeStateStore,
8} from "./storage";
9import {
10 type LocalScopeState,
11} from "../scope-state";
6 12
7const trimOrNull = (value: string | null | undefined): string | null => {13const trimOrNull = (value: string | null | undefined): string | null => {
8 if (value == null) {14 if (value == null) {
@@ -35,10 +41,10 @@ export const derivePolicyConfigKey = ( · 26 unmodified lines
35 41
36const pruneLocalWorkspaceState = (input: {42const pruneLocalWorkspaceState = (input: {
37 loadedConfig: LoadedLocalExecutorConfig;43 loadedConfig: LoadedLocalExecutorConfig;
38}): Effect.Effect<LocalWorkspaceState, Error, WorkspaceStateStore> =>44}): Effect.Effect<LocalScopeState, Error, ScopeStateStore> =>
39 Effect.gen(function* () {45 Effect.gen(function* () {
40 const workspaceStateStore = yield* WorkspaceStateStore;46 const scopeStateStore = yield* ScopeStateStore;
41 const currentState = yield* workspaceStateStore.load();47 const currentState = yield* scopeStateStore.load();
42 48
43 const configuredSourceIds = new Set(49 const configuredSourceIds = new Set(
44 Object.keys(input.loadedConfig.config?.sources ?? {}),50 Object.keys(input.loadedConfig.config?.sources ?? {}),
@@ -47,7 +53,7 @@ const pruneLocalWorkspaceState = (input: { · 2 unmodified lines
47 Object.keys(input.loadedConfig.config?.policies ?? {}),53 Object.keys(input.loadedConfig.config?.policies ?? {}),
48 );54 );
49 55
50 const nextState: LocalWorkspaceState = {56 const nextState: LocalScopeState = {
51 ...currentState,57 ...currentState,
52 sources: Object.fromEntries(58 sources: Object.fromEntries(
53 Object.entries(currentState.sources).filter(([sourceId]) =>59 Object.entries(currentState.sources).filter(([sourceId]) =>
@@ -65,16 +71,16 @@ const pruneLocalWorkspaceState = (input: { · 11 unmodified lines
65 return currentState;71 return currentState;
66 }72 }
67 73
68 yield* workspaceStateStore.write({74 yield* scopeStateStore.write({
69 state: nextState,75 state: nextState,
70 });76 });
71 77
72 return nextState;78 return nextState;
73 });79 });
74 80
75export const synchronizeLocalWorkspaceState = (input: {81export const synchronizeLocalScopeState = (input: {
76 loadedConfig: LoadedLocalExecutorConfig;82 loadedConfig: LoadedLocalExecutorConfig;
77}): Effect.Effect<LoadedLocalExecutorConfig["config"], Error, WorkspaceStateStore> =>83}): Effect.Effect<LoadedLocalExecutorConfig["config"], Error, ScopeStateStore> =>
78 Effect.gen(function* () {84 Effect.gen(function* () {
79 yield* pruneLocalWorkspaceState({85 yield* pruneLocalWorkspaceState({
80 loadedConfig: input.loadedConfig,86 loadedConfig: input.loadedConfig,
packages/platform/sdk/src/runtime/scope/secret-material-providers.tsrenamed from packages/platform/sdk/src/runtime/workspace/secret-material-providers.ts+3−1
@@ -1,4 +1,6 @@
1import type { InstanceConfig } from "../../local/contracts";1import type {
2 InstanceConfig,
3} from "../../local/contracts";
2import * as Context from "effect/Context";4import * as Context from "effect/Context";
3import * as Effect from "effect/Effect";5import * as Effect from "effect/Effect";
4 6
packages/platform/sdk/src/runtime/scope/storage.tsrenamed from packages/platform/sdk/src/runtime/workspace/storage.ts+43−30
@@ -1,12 +1,25 @@
1import type { LocalInstallation, LocalExecutorConfig } from "#schema";1import type {
2 LocalInstallation,
3 LocalExecutorConfig,
4} from "#schema";
2import * as Context from "effect/Context";5import * as Context from "effect/Context";
3import * as Layer from "effect/Layer";6import * as Layer from "effect/Layer";
4 7
5import type { LoadedLocalExecutorConfig } from "../workspace-config";8import type {
6import type { LocalSourceArtifact } from "../source-artifacts";9 LoadedLocalExecutorConfig,
7import type { LocalWorkspaceState } from "../workspace-state";10} from "../scope-config";
8import type { SourceCatalogSyncResult } from "@executor/source-core";11import type {
9import type { Source } from "#schema";12 LocalSourceArtifact,
13} from "../source-artifacts";
14import type {
15 LocalScopeState,
16} from "../scope-state";
17import type {
18 SourceCatalogSyncResult,
19} from "@executor/source-core";
20import type {
21 Source,
22} from "#schema";
10 23
11export type InstallationStoreShape = {24export type InstallationStoreShape = {
12 load: () => import("effect/Effect").Effect<LocalInstallation, Error, never>;25 load: () => import("effect/Effect").Effect<LocalInstallation, Error, never>;
@@ -17,28 +30,28 @@ export class InstallationStore extends Context.Tag( · 4 unmodified lines
17 "#runtime/InstallationStore",30 "#runtime/InstallationStore",
18)<InstallationStore, InstallationStoreShape>() {}31)<InstallationStore, InstallationStoreShape>() {}
19 32
20export type WorkspaceConfigStoreShape = {33export type ScopeConfigStoreShape = {
21 load: () => import("effect/Effect").Effect<LoadedLocalExecutorConfig, Error, never>;34 load: () => import("effect/Effect").Effect<LoadedLocalExecutorConfig, Error, never>;
22 writeProject: (input: {35 writeProject: (input: {
23 config: LocalExecutorConfig;36 config: LocalExecutorConfig;
24 }) => import("effect/Effect").Effect<void, Error, never>;37 }) => import("effect/Effect").Effect<void, Error, never>;
25 resolveRelativePath: (input: { path: string; workspaceRoot: string }) => string;38 resolveRelativePath: (input: { path: string; scopeRoot: string }) => string;
26};39};
27 40
28export class WorkspaceConfigStore extends Context.Tag(41export class ScopeConfigStore extends Context.Tag(
29 "#runtime/WorkspaceConfigStore",42 "#runtime/ScopeConfigStore",
30)<WorkspaceConfigStore, WorkspaceConfigStoreShape>() {}43)<ScopeConfigStore, ScopeConfigStoreShape>() {}
31 44
32export type WorkspaceStateStoreShape = {45export type ScopeStateStoreShape = {
33 load: () => import("effect/Effect").Effect<LocalWorkspaceState, Error, never>;46 load: () => import("effect/Effect").Effect<LocalScopeState, Error, never>;
34 write: (input: {47 write: (input: {
35 state: LocalWorkspaceState;48 state: LocalScopeState;
36 }) => import("effect/Effect").Effect<void, Error, never>;49 }) => import("effect/Effect").Effect<void, Error, never>;
37};50};
38 51
39export class WorkspaceStateStore extends Context.Tag(52export class ScopeStateStore extends Context.Tag(
40 "#runtime/WorkspaceStateStore",53 "#runtime/ScopeStateStore",
41)<WorkspaceStateStore, WorkspaceStateStoreShape>() {}54)<ScopeStateStore, ScopeStateStoreShape>() {}
42 55
43export type SourceArtifactStoreShape = {56export type SourceArtifactStoreShape = {
44 build: (input: {57 build: (input: {
@@ -63,33 +76,33 @@ export class SourceArtifactStore extends Context.Tag( · 18 unmodified lines
63 76
64export type LocalStorageServices =77export type LocalStorageServices =
65 | InstallationStore78 | InstallationStore
66 | WorkspaceConfigStore79 | ScopeConfigStore
67 | WorkspaceStateStore80 | ScopeStateStore
68 | SourceArtifactStore;81 | SourceArtifactStore;
69 82
70export type WorkspaceStorageServices =83export type ScopeStorageServices =
71 | WorkspaceConfigStore84 | ScopeConfigStore
72 | WorkspaceStateStore85 | ScopeStateStore
73 | SourceArtifactStore;86 | SourceArtifactStore;
74 87
75export const makeWorkspaceStorageLayer = (input: {88export const makeScopeStorageLayer = (input: {
76 workspaceConfigStore: WorkspaceConfigStoreShape;89 scopeConfigStore: ScopeConfigStoreShape;
77 workspaceStateStore: WorkspaceStateStoreShape;90 scopeStateStore: ScopeStateStoreShape;
78 sourceArtifactStore: SourceArtifactStoreShape;91 sourceArtifactStore: SourceArtifactStoreShape;
79}) =>92}) =>
80 Layer.mergeAll(93 Layer.mergeAll(
81 Layer.succeed(WorkspaceConfigStore, input.workspaceConfigStore),94 Layer.succeed(ScopeConfigStore, input.scopeConfigStore),
82 Layer.succeed(WorkspaceStateStore, input.workspaceStateStore),95 Layer.succeed(ScopeStateStore, input.scopeStateStore),
83 Layer.succeed(SourceArtifactStore, input.sourceArtifactStore),96 Layer.succeed(SourceArtifactStore, input.sourceArtifactStore),
84 );97 );
85 98
86export const makeLocalStorageLayer = (input: {99export const makeLocalStorageLayer = (input: {
87 installationStore: InstallationStoreShape;100 installationStore: InstallationStoreShape;
88 workspaceConfigStore: WorkspaceConfigStoreShape;101 scopeConfigStore: ScopeConfigStoreShape;
89 workspaceStateStore: WorkspaceStateStoreShape;102 scopeStateStore: ScopeStateStoreShape;
90 sourceArtifactStore: SourceArtifactStoreShape;103 sourceArtifactStore: SourceArtifactStoreShape;
91}) =>104}) =>
92 Layer.mergeAll(105 Layer.mergeAll(
93 Layer.succeed(InstallationStore, input.installationStore),106 Layer.succeed(InstallationStore, input.installationStore),
94 makeWorkspaceStorageLayer(input),107 makeScopeStorageLayer(input),
95 );108 );
packages/platform/sdk/src/runtime/source-artifacts.ts+3−1
@@ -9,7 +9,9 @@ import { · 8 unmodified lines
9import * as Option from "effect/Option";9import * as Option from "effect/Option";
10import * as Schema from "effect/Schema";10import * as Schema from "effect/Schema";
11 11
12import { decodeCatalogSnapshotV1 } from "@executor/ir/catalog";12import {
13 decodeCatalogSnapshotV1,
14} from "@executor/ir/catalog";
13import {15import {
14 CatalogSnapshotV1Schema,16 CatalogSnapshotV1Schema,
15 type CatalogSnapshotV1,17 type CatalogSnapshotV1,
packages/platform/sdk/src/runtime/sources/executor-tools.ts+67−60
@@ -8,45 +8,50 @@ import { · 7 unmodified lines
8 type ToolPath,8 type ToolPath,
9} from "@executor/codemode-core";9} from "@executor/codemode-core";
10import {10import {
11 type AccountId,11 type ScopeId,
12 ExecutionIdSchema,12 ExecutionIdSchema,
13 ExecutionInteractionIdSchema,13 ExecutionInteractionIdSchema,
14 SourceSchema,14 SourceSchema,
15 type Source,15 type Source,
16 type WorkspaceId,
17} from "#schema";16} from "#schema";
18import * as Effect from "effect/Effect";17import * as Effect from "effect/Effect";
19import * as Cause from "effect/Cause";18import * as Cause from "effect/Cause";
20import * as Exit from "effect/Exit";19import * as Exit from "effect/Exit";
21import * as Schema from "effect/Schema";20import * as Schema from "effect/Schema";
22import type { RuntimeLocalWorkspaceState } from "../workspace/runtime-context";21import type {
22 RuntimeLocalScopeState,
23} from "../scope/runtime-context";
23import {24import {
24 type LocalStorageServices,25 type LocalStorageServices,
25 type InstallationStoreShape,26 type InstallationStoreShape,
26 type SourceArtifactStoreShape,27 type SourceArtifactStoreShape,
27 type WorkspaceConfigStoreShape,28 type ScopeConfigStoreShape,
28 type WorkspaceStateStoreShape,29 type ScopeStateStoreShape,
29 makeLocalStorageLayer,30 makeLocalStorageLayer,
30} from "../workspace/storage";31} from "../scope/storage";
31import { provideOptionalRuntimeLocalWorkspace } from "../workspace/runtime-context";32import {
32import { runtimeEffectError } from "../effect-errors";33 provideOptionalRuntimeLocalScope,
34} from "../scope/runtime-context";
35import {
36 runtimeEffectError,
37} from "../effect-errors";
33 38
34/** Run an Effect as a Promise, preserving the original error (not FiberFailure). */39/** Run an Effect as a Promise, preserving the original error (not FiberFailure). */
35const runEffect = async <A>(40const runEffect = async <A>(
36 effect: Effect.Effect<A, unknown, LocalStorageServices>,41 effect: Effect.Effect<A, unknown, LocalStorageServices>,
37 storage: {42 storage: {
38 installationStore: InstallationStoreShape;43 installationStore: InstallationStoreShape;
39 workspaceConfigStore: WorkspaceConfigStoreShape;44 scopeConfigStore: ScopeConfigStoreShape;
40 workspaceStateStore: WorkspaceStateStoreShape;45 scopeStateStore: ScopeStateStoreShape;
41 sourceArtifactStore: SourceArtifactStoreShape;46 sourceArtifactStore: SourceArtifactStoreShape;
42 },47 },
43 runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null = null,48 runtimeLocalScope: RuntimeLocalScopeState | null = null,
44): Promise<A> => {49): Promise<A> => {
45 const baseLayer = makeLocalStorageLayer(storage);50 const baseLayer = makeLocalStorageLayer(storage);
46 const exit = await Effect.runPromiseExit(51 const exit = await Effect.runPromiseExit(
47 provideOptionalRuntimeLocalWorkspace(52 provideOptionalRuntimeLocalScope(
48 effect.pipe(Effect.provide(baseLayer)),53 effect.pipe(Effect.provide(baseLayer)),
49 runtimeLocalWorkspace,54 runtimeLocalScope,
50 ),55 ),
51 );56 );
52 if (Exit.isSuccess(exit)) return exit.value;57 if (Exit.isSuccess(exit)) return exit.value;
@@ -69,7 +74,9 @@ import { · 16 unmodified lines
69 executorAddableSourceAdapters,74 executorAddableSourceAdapters,
70 sourceAdapterRequiresInteractiveConnect,75 sourceAdapterRequiresInteractiveConnect,
71} from "./source-adapters";76} from "./source-adapters";
72import { decodeSourceCredentialSelectionContent } from "./source-credential-interactions";77import {
78 decodeSourceCredentialSelectionContent,
79} from "./source-credential-interactions";
73 80
74const ExecutorSourcesAddInputSchema = Schema.standardSchemaV1(81const ExecutorSourcesAddInputSchema = Schema.standardSchemaV1(
75 ExecutorAddSourceInputSchema,82 ExecutorAddSourceInputSchema,
@@ -112,7 +119,7 @@ export const EXECUTOR_SOURCES_ADD_HELP_LINES = [ · 36 unmodified lines
112 119
113export const buildExecutorSourcesAddDescription = (): string =>120export const buildExecutorSourcesAddDescription = (): string =>
114 [121 [
115 "Add an MCP, OpenAPI, or GraphQL source to the current workspace.",122 "Add an MCP, OpenAPI, or GraphQL source to the current scope.",
116 ...EXECUTOR_SOURCES_ADD_HELP_LINES,123 ...EXECUTOR_SOURCES_ADD_HELP_LINES,
117 ].join("\n");124 ].join("\n");
118 125
@@ -130,29 +137,29 @@ const toSerializableValue = <A>(value: A): A => · 11 unmodified lines
130 JSON.parse(JSON.stringify(value)) as A;137 JSON.parse(JSON.stringify(value)) as A;
131 138
132type ExecutorSourcesAddToolArgs =139type ExecutorSourcesAddToolArgs =
133 | Omit<ExecutorMcpSourceInput, "workspaceId" | "actorAccountId" | "executionId" | "interactionId">140 | Omit<ExecutorMcpSourceInput, "scopeId" | "actorScopeId" | "executionId" | "interactionId">
134 | Omit<141 | Omit<
135 ExecutorCredentialManagedSourceInput,142 ExecutorCredentialManagedSourceInput,
136 "workspaceId" | "actorAccountId" | "executionId" | "interactionId"143 "scopeId" | "actorScopeId" | "executionId" | "interactionId"
137 >;144 >;
138 145
139type ExecutorGoogleDiscoveryToolArgs = Omit<146type ExecutorGoogleDiscoveryToolArgs = Omit<
140 Extract<ExecutorCredentialManagedSourceInput, { service: string; version: string }>,147 Extract<ExecutorCredentialManagedSourceInput, { service: string; version: string }>,
141 "workspaceId" | "actorAccountId" | "executionId" | "interactionId"148 "scopeId" | "actorScopeId" | "executionId" | "interactionId"
142>;149>;
143 150
144type ExecutorOpenApiToolArgs = Omit<151type ExecutorOpenApiToolArgs = Omit<
145 Extract<ExecutorCredentialManagedSourceInput, { specUrl: string }>,152 Extract<ExecutorCredentialManagedSourceInput, { specUrl: string }>,
146 "workspaceId" | "actorAccountId" | "executionId" | "interactionId"153 "scopeId" | "actorScopeId" | "executionId" | "interactionId"
147>;154>;
148 155
149type ExecutorGraphqlToolArgs = Omit<156type ExecutorGraphqlToolArgs = Omit<
150 Extract<ExecutorCredentialManagedSourceInput, { kind: "graphql" }>,157 Extract<ExecutorCredentialManagedSourceInput, { kind: "graphql" }>,
151 "workspaceId" | "actorAccountId" | "executionId" | "interactionId"158 "scopeId" | "actorScopeId" | "executionId" | "interactionId"
152>;159>;
153 160
154type ExecutorCredentialPromptArgs = {161type ExecutorCredentialPromptArgs = {
155 workspaceId: WorkspaceId;162 scopeId: ScopeId;
156 sourceId: Source["id"];163 sourceId: Source["id"];
157 kind: ExecutorCredentialManagedSourceInput["kind"];164 kind: ExecutorCredentialManagedSourceInput["kind"];
158 endpoint?: string;165 endpoint?: string;
@@ -166,7 +173,7 @@ type ExecutorCredentialPromptArgs = { · 7 unmodified lines
166 173
167const isExecutorMcpToolArgs = (174const isExecutorMcpToolArgs = (
168 args: ExecutorSourcesAddToolArgs,175 args: ExecutorSourcesAddToolArgs,
169): args is Omit<ExecutorMcpSourceInput, "workspaceId" | "actorAccountId" | "executionId" | "interactionId"> =>176): args is Omit<ExecutorMcpSourceInput, "scopeId" | "actorScopeId" | "executionId" | "interactionId"> =>
170 args.kind === undefined || sourceAdapterRequiresInteractiveConnect(args.kind);177 args.kind === undefined || sourceAdapterRequiresInteractiveConnect(args.kind);
171 178
172const isExecutorCredentialManagedSourceInput = (179const isExecutorCredentialManagedSourceInput = (
@@ -176,8 +183,8 @@ const isExecutorCredentialManagedSourceInput = ( · 3 unmodified lines
176 183
177const prepareExecutorAddSourceInput = (input: {184const prepareExecutorAddSourceInput = (input: {
178 args: ExecutorSourcesAddToolArgs;185 args: ExecutorSourcesAddToolArgs;
179 workspaceId: WorkspaceId;186 scopeId: ScopeId;
180 accountId: AccountId;187 actorScopeId: ScopeId;
181 executionId: ReturnType<typeof toExecutionId>;188 executionId: ReturnType<typeof toExecutionId>;
182 interactionId: ReturnType<typeof ExecutionInteractionIdSchema.make>;189 interactionId: ReturnType<typeof ExecutionInteractionIdSchema.make>;
183}): ExecutorAddSourceInput => {190}): ExecutorAddSourceInput => {
@@ -194,8 +201,8 @@ const prepareExecutorAddSourceInput = (input: { · 10 unmodified lines
194 args: input.args.args ?? null,201 args: input.args.args ?? null,
195 env: input.args.env ?? null,202 env: input.args.env ?? null,
196 cwd: input.args.cwd ?? null,203 cwd: input.args.cwd ?? null,
197 workspaceId: input.workspaceId,204 scopeId: input.scopeId,
198 actorAccountId: input.accountId,205 actorScopeId: input.actorScopeId,
199 executionId: input.executionId,206 executionId: input.executionId,
200 interactionId: input.interactionId,207 interactionId: input.interactionId,
201 } satisfies ExecutorMcpSourceInput;208 } satisfies ExecutorMcpSourceInput;
@@ -205,8 +212,8 @@ const prepareExecutorAddSourceInput = (input: { · 3 unmodified lines
205 const args = input.args as ExecutorGoogleDiscoveryToolArgs;212 const args = input.args as ExecutorGoogleDiscoveryToolArgs;
206 return {213 return {
207 ...args,214 ...args,
208 workspaceId: input.workspaceId,215 scopeId: input.scopeId,
209 actorAccountId: input.accountId,216 actorScopeId: input.actorScopeId,
210 executionId: input.executionId,217 executionId: input.executionId,
211 interactionId: input.interactionId,218 interactionId: input.interactionId,
212 } satisfies Extract<ExecutorCredentialManagedSourceInput, { service: string; version: string }>;219 } satisfies Extract<ExecutorCredentialManagedSourceInput, { service: string; version: string }>;
@@ -216,8 +223,8 @@ const prepareExecutorAddSourceInput = (input: { · 3 unmodified lines
216 const args = input.args as ExecutorOpenApiToolArgs;223 const args = input.args as ExecutorOpenApiToolArgs;
217 return {224 return {
218 ...args,225 ...args,
219 workspaceId: input.workspaceId,226 scopeId: input.scopeId,
220 actorAccountId: input.accountId,227 actorScopeId: input.actorScopeId,
221 executionId: input.executionId,228 executionId: input.executionId,
222 interactionId: input.interactionId,229 interactionId: input.interactionId,
223 } satisfies Extract<ExecutorCredentialManagedSourceInput, { specUrl: string }>;230 } satisfies Extract<ExecutorCredentialManagedSourceInput, { specUrl: string }>;
@@ -226,8 +233,8 @@ const prepareExecutorAddSourceInput = (input: { · 2 unmodified lines
226 const args = input.args as ExecutorGraphqlToolArgs;233 const args = input.args as ExecutorGraphqlToolArgs;
227 return {234 return {
228 ...args,235 ...args,
229 workspaceId: input.workspaceId,236 scopeId: input.scopeId,
230 actorAccountId: input.accountId,237 actorScopeId: input.actorScopeId,
231 executionId: input.executionId,238 executionId: input.executionId,
232 interactionId: input.interactionId,239 interactionId: input.interactionId,
233 } satisfies Extract<ExecutorCredentialManagedSourceInput, { kind: "graphql" }>;240 } satisfies Extract<ExecutorCredentialManagedSourceInput, { kind: "graphql" }>;
@@ -235,13 +242,13 @@ const prepareExecutorAddSourceInput = (input: { · 1 unmodified line
235 242
236const resolveLocalCredentialUrl = (input: {243const resolveLocalCredentialUrl = (input: {
237 baseUrl: string;244 baseUrl: string;
238 workspaceId: WorkspaceId;245 scopeId: ScopeId;
239 sourceId: Source["id"];246 sourceId: Source["id"];
240 executionId: string;247 executionId: string;
241 interactionId: string;248 interactionId: string;
242}): string =>249}): string =>
243 new URL(250 new URL(
244 `/v1/workspaces/${encodeURIComponent(input.workspaceId)}/sources/${encodeURIComponent(input.sourceId)}/credentials?interactionId=${encodeURIComponent(`${input.executionId}:${input.interactionId}`)}`,251 `/v1/workspaces/${encodeURIComponent(input.scopeId)}/sources/${encodeURIComponent(input.sourceId)}/credentials?interactionId=${encodeURIComponent(`${input.executionId}:${input.interactionId}`)}`,
245 input.baseUrl,252 input.baseUrl,
246 ).toString();253 ).toString();
247 254
@@ -282,7 +289,7 @@ const promptForSourceCredentialSelection = (input: { · 34 unmodified lines
282 : `Open the secure credential page to connect ${input.source.name}`,289 : `Open the secure credential page to connect ${input.source.name}`,
283 url: resolveLocalCredentialUrl({290 url: resolveLocalCredentialUrl({
284 baseUrl: input.localServerBaseUrl,291 baseUrl: input.localServerBaseUrl,
285 workspaceId: input.args.workspaceId,292 scopeId: input.args.scopeId,
286 sourceId: input.args.sourceId,293 sourceId: input.args.sourceId,
287 executionId: input.executionId,294 executionId: input.executionId,
288 interactionId: input.interactionId,295 interactionId: input.interactionId,
@@ -312,14 +319,14 @@ const promptForSourceCredentialSelection = (input: { · 23 unmodified lines
312 });319 });
313 320
314export const createExecutorToolMap = (input: {321export const createExecutorToolMap = (input: {
315 workspaceId: WorkspaceId;322 scopeId: ScopeId;
316 accountId: AccountId;323 actorScopeId: ScopeId;
317 sourceAuthService: RuntimeSourceAuthService;324 sourceAuthService: RuntimeSourceAuthService;
318 installationStore: InstallationStoreShape;325 installationStore: InstallationStoreShape;
319 workspaceConfigStore: WorkspaceConfigStoreShape;326 scopeConfigStore: ScopeConfigStoreShape;
320 workspaceStateStore: WorkspaceStateStoreShape;327 scopeStateStore: ScopeStateStoreShape;
321 sourceArtifactStore: SourceArtifactStoreShape;328 sourceArtifactStore: SourceArtifactStoreShape;
322 runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null;329 runtimeLocalScope: RuntimeLocalScopeState | null;
323}): ToolMap => ({330}): ToolMap => ({
324 "executor.sources.add": toTool({331 "executor.sources.add": toTool({
325 tool: {332 tool: {
@@ -333,8 +340,8 @@ export const createExecutorToolMap = (input: { · 7 unmodified lines
333 );340 );
334 const preparedArgs = prepareExecutorAddSourceInput({341 const preparedArgs = prepareExecutorAddSourceInput({
335 args,342 args,
336 workspaceId: input.workspaceId,343 scopeId: input.scopeId,
337 accountId: input.accountId,344 actorScopeId: input.actorScopeId,
338 executionId,345 executionId,
339 interactionId,346 interactionId,
340 });347 });
@@ -356,11 +363,11 @@ export const createExecutorToolMap = (input: { · 15 unmodified lines
356 ),363 ),
357 {364 {
358 installationStore: input.installationStore,365 installationStore: input.installationStore,
359 workspaceConfigStore: input.workspaceConfigStore,366 scopeConfigStore: input.scopeConfigStore,
360 workspaceStateStore: input.workspaceStateStore,367 scopeStateStore: input.scopeStateStore,
361 sourceArtifactStore: input.sourceArtifactStore,368 sourceArtifactStore: input.sourceArtifactStore,
362 },369 },
363 input.runtimeLocalWorkspace,370 input.runtimeLocalScope,
364 );371 );
365 372
366 if (result.kind === "connected") {373 if (result.kind === "connected") {
@@ -379,7 +386,7 @@ export const createExecutorToolMap = (input: { · 12 unmodified lines
379 promptForSourceCredentialSelection({386 promptForSourceCredentialSelection({
380 args: {387 args: {
381 ...pendingArgs,388 ...pendingArgs,
382 workspaceId: input.workspaceId,389 scopeId: input.scopeId,
383 sourceId: pendingResult.source.id,390 sourceId: pendingResult.source.id,
384 },391 },
385 credentialSlot: pendingResult.credentialSlot,392 credentialSlot: pendingResult.credentialSlot,
@@ -395,11 +402,11 @@ export const createExecutorToolMap = (input: { · 9 unmodified lines
395 }),402 }),
396 {403 {
397 installationStore: input.installationStore,404 installationStore: input.installationStore,
398 workspaceConfigStore: input.workspaceConfigStore,405 scopeConfigStore: input.scopeConfigStore,
399 workspaceStateStore: input.workspaceStateStore,406 scopeStateStore: input.scopeStateStore,
400 sourceArtifactStore: input.sourceArtifactStore,407 sourceArtifactStore: input.sourceArtifactStore,
401 },408 },
402 input.runtimeLocalWorkspace,409 input.runtimeLocalScope,
403 );410 );
404 411
405 pendingArgs = pendingResult.credentialSlot === "import"412 pendingArgs = pendingResult.credentialSlot === "import"
@@ -431,11 +438,11 @@ export const createExecutorToolMap = (input: { · 25 unmodified lines
431 ),438 ),
432 {439 {
433 installationStore: input.installationStore,440 installationStore: input.installationStore,
434 workspaceConfigStore: input.workspaceConfigStore,441 scopeConfigStore: input.scopeConfigStore,
435 workspaceStateStore: input.workspaceStateStore,442 scopeStateStore: input.scopeStateStore,
436 sourceArtifactStore: input.sourceArtifactStore,443 sourceArtifactStore: input.sourceArtifactStore,
437 },444 },
438 input.runtimeLocalWorkspace,445 input.runtimeLocalScope,
439 );446 );
440 447
441 if (completed.kind === "connected") {448 if (completed.kind === "connected") {
@@ -481,11 +488,11 @@ export const createExecutorToolMap = (input: { · 39 unmodified lines
481 }),488 }),
482 {489 {
483 installationStore: input.installationStore,490 installationStore: input.installationStore,
484 workspaceConfigStore: input.workspaceConfigStore,491 scopeConfigStore: input.scopeConfigStore,
485 workspaceStateStore: input.workspaceStateStore,492 scopeStateStore: input.scopeStateStore,
486 sourceArtifactStore: input.sourceArtifactStore,493 sourceArtifactStore: input.sourceArtifactStore,
487 },494 },
488 input.runtimeLocalWorkspace,495 input.runtimeLocalScope,
489 );496 );
490 497
491 if (response.action !== "accept") {498 if (response.action !== "accept") {
@@ -494,17 +501,17 @@ export const createExecutorToolMap = (input: { · 2 unmodified lines
494 501
495 const connected = await runEffect(502 const connected = await runEffect(
496 input.sourceAuthService.getSourceById({503 input.sourceAuthService.getSourceById({
497 workspaceId: input.workspaceId,504 scopeId: input.scopeId,
498 sourceId: result.source.id,505 sourceId: result.source.id,
499 actorAccountId: input.accountId,506 actorScopeId: input.actorScopeId,
500 }),507 }),
501 {508 {
502 installationStore: input.installationStore,509 installationStore: input.installationStore,
503 workspaceConfigStore: input.workspaceConfigStore,510 scopeConfigStore: input.scopeConfigStore,
504 workspaceStateStore: input.workspaceStateStore,511 scopeStateStore: input.scopeStateStore,
505 sourceArtifactStore: input.sourceArtifactStore,512 sourceArtifactStore: input.sourceArtifactStore,
506 },513 },
507 input.runtimeLocalWorkspace,514 input.runtimeLocalScope,
508 );515 );
509 return toSerializableValue(connected);516 return toSerializableValue(connected);
510 },517 },
packages/platform/sdk/src/runtime/sources/graphql-tools.test.ts+13−3
@@ -1,8 +1,18 @@
1import { createServer, type IncomingMessage } from "node:http";1import {
2 createServer,
3 type IncomingMessage,
4} from "node:http";
2 5
3import { afterEach, describe, expect, it } from "@effect/vitest";6import {
7 afterEach,
8 describe,
9 expect,
10 it,
11} from "@effect/vitest";
4 12
5import { createGraphqlToolFromPersistedOperation } from "./graphql-tools";13import {
14 createGraphqlToolFromPersistedOperation,
15} from "./graphql-tools";
6 16
7type CapturedRequest = {17type CapturedRequest = {
8 path: string;18 path: string;
packages/platform/sdk/src/runtime/sources/source-adapter-fixture-matrix.test.ts+47−23
@@ -1,5 +1,9 @@
1import { createServer } from "node:http";1import {
2import { fileURLToPath } from "node:url";2 createServer,
3} from "node:http";
4import {
5 fileURLToPath,
6} from "node:url";
3 7
4import {8import {
5 FileSystem,9 FileSystem,
@@ -9,7 +13,9 @@ import { · 3 unmodified lines
9 HttpApiSchema,13 HttpApiSchema,
10 OpenApi,14 OpenApi,
11} from "@effect/platform";15} from "@effect/platform";
12import { NodeFileSystem } from "@effect/platform-node";16import {
17 NodeFileSystem,
18} from "@effect/platform-node";
13import {19import {
14 buildOpenApiToolPresentation,20 buildOpenApiToolPresentation,
15 compileOpenApiToolDefinitions,21 compileOpenApiToolDefinitions,
@@ -21,7 +27,11 @@ import { · 5 unmodified lines
21 compileGoogleDiscoveryToolDefinitions,27 compileGoogleDiscoveryToolDefinitions,
22 extractGoogleDiscoveryManifest,28 extractGoogleDiscoveryManifest,
23} from "@executor/source-google-discovery";29} from "@executor/source-google-discovery";
24import { describe, expect, it } from "@effect/vitest";30import {
31 describe,
32 expect,
33 it,
34} from "@effect/vitest";
25 35
26import type {36import type {
27 Source,37 Source,
@@ -32,13 +42,19 @@ import { · 4 unmodified lines
32 SourceCatalogIdSchema,42 SourceCatalogIdSchema,
33 SourceCatalogRevisionIdSchema,43 SourceCatalogRevisionIdSchema,
34 SourceIdSchema,44 SourceIdSchema,
35 WorkspaceIdSchema,45 ScopeIdSchema,
36} from "#schema";46} from "#schema";
37import * as Effect from "effect/Effect";47import * as Effect from "effect/Effect";
38import { Schema } from "effect";48import {
49 Schema,
50} from "effect";
39 51
40import { projectCatalogForAgentSdk } from "@executor/ir/catalog";52import {
41import type { CatalogSnapshotV1 } from "@executor/ir/model";53 projectCatalogForAgentSdk,
54} from "@executor/ir/catalog";
55import type {
56 CatalogSnapshotV1,
57} from "@executor/ir/model";
42import {58import {
43 createCatalogTypeProjector,59 createCatalogTypeProjector,
44 projectedCatalogTypeRoots,60 projectedCatalogTypeRoots,
@@ -52,12 +68,20 @@ import { · 7 unmodified lines
52 expandCatalogToolByPath,68 expandCatalogToolByPath,
53 type LoadedSourceCatalog,69 type LoadedSourceCatalog,
54} from "../catalog/source/runtime";70} from "../catalog/source/runtime";
55import { invokeIrTool } from "../execution/ir-execution";71import {
56import { createGoogleDiscoveryCatalogSnapshot } from "@executor/source-google-discovery";72 invokeIrTool,
57import { createGraphqlCatalogSnapshot } from "@executor/source-graphql";73} from "../execution/ir-execution";
58import { createOpenApiCatalogSnapshot } from "@executor/source-openapi";74import {
75 createGoogleDiscoveryCatalogSnapshot,
76} from "@executor/source-google-discovery";
77import {
78 createGraphqlCatalogSnapshot,
79} from "@executor/source-graphql";
80import {
81 createOpenApiCatalogSnapshot,
82} from "@executor/source-openapi";
59 83
60const FIXTURE_WORKSPACE_ID = WorkspaceIdSchema.make("ws_source_fixture_matrix");84const FIXTURE_WORKSPACE_ID = ScopeIdSchema.make("ws_source_fixture_matrix");
61 85
62const readFixture = (name: string) =>86const readFixture = (name: string) =>
63 FileSystem.FileSystem.pipe(87 FileSystem.FileSystem.pipe(
@@ -79,7 +103,7 @@ const makeSource = (input: { · 15 unmodified lines
79 binding: Source["binding"];103 binding: Source["binding"];
80}): Source => ({104}): Source => ({
81 id: SourceIdSchema.make(input.id),105 id: SourceIdSchema.make(input.id),
82 workspaceId: FIXTURE_WORKSPACE_ID,106 scopeId: FIXTURE_WORKSPACE_ID,
83 name: input.name,107 name: input.name,
84 kind: input.kind,108 kind: input.kind,
85 endpoint: input.endpoint,109 endpoint: input.endpoint,
@@ -107,7 +131,7 @@ const makeLoadedCatalog = (input: { · 21 unmodified lines
107 );131 );
108 const sourceRecord = {132 const sourceRecord = {
109 id: input.source.id,133 id: input.source.id,
110 workspaceId: input.source.workspaceId,134 scopeId: input.source.scopeId,
111 catalogId,135 catalogId,
112 catalogRevisionId: revisionId,136 catalogRevisionId: revisionId,
113 name: input.source.name,137 name: input.source.name,
@@ -725,8 +749,8 @@ describe("source adapter fixture matrix", () => { · 611 unmodified lines
725 749
726 const getItemResult = await Effect.runPromise(750 const getItemResult = await Effect.runPromise(
727 invokeIrTool({751 invokeIrTool({
728 workspaceId: source.workspaceId,752 scopeId: source.scopeId,
729 accountId: "acct_fixture" as any,753 actorScopeId: "acct_fixture" as any,
730 tool: getItemTool,754 tool: getItemTool,
731 auth,755 auth,
732 args: {756 args: {
@@ -741,8 +765,8 @@ describe("source adapter fixture matrix", () => { · 8 unmodified lines
741 );765 );
742 const submitFormResult = await Effect.runPromise(766 const submitFormResult = await Effect.runPromise(
743 invokeIrTool({767 invokeIrTool({
744 workspaceId: source.workspaceId,768 scopeId: source.scopeId,
745 accountId: "acct_fixture" as any,769 actorScopeId: "acct_fixture" as any,
746 tool: submitFormTool,770 tool: submitFormTool,
747 auth,771 auth,
748 args: {772 args: {
@@ -898,8 +922,8 @@ describe("source adapter fixture matrix", () => { · 149 unmodified lines
898 922
899 const result = await Effect.runPromise(923 const result = await Effect.runPromise(
900 invokeIrTool({924 invokeIrTool({
901 workspaceId: source.workspaceId,925 scopeId: source.scopeId,
902 accountId: "acct_fixture" as any,926 actorScopeId: "acct_fixture" as any,
903 tool,927 tool,
904 auth,928 auth,
905 args: {929 args: {
@@ -1332,8 +1356,8 @@ describe("source adapter fixture matrix", () => { · 426 unmodified lines
1332 } as const;1356 } as const;
1333 1357
1334 const result = yield* invokeIrTool({1358 const result = yield* invokeIrTool({
1335 workspaceId: source.workspaceId,1359 scopeId: source.scopeId,
1336 accountId: "acct_fixture" as any,1360 actorScopeId: "acct_fixture" as any,
1337 tool,1361 tool,
1338 auth,1362 auth,
1339 args: {},1363 args: {},
packages/platform/sdk/src/runtime/sources/source-adapters/google-discovery.test.ts+24−8
@@ -1,14 +1,30 @@
1import { createServer } from "node:http";1import {
2 createServer,
3} from "node:http";
2 4
3import { describe, expect, it } from "@effect/vitest";5import {
4import { SourceIdSchema } from "#schema";6 describe,
7 expect,
8 it,
9} from "@effect/vitest";
10import {
11 SourceIdSchema,
12} from "#schema";
5 13
6import * as Effect from "effect/Effect";14import * as Effect from "effect/Effect";
7 15
8import { googleDiscoverySourceAdapter } from "./google-discovery";16import {
9import { snapshotFromSourceCatalogSyncResult } from "../catalog-sync-result";17 googleDiscoverySourceAdapter,
10import { createSourceFromPayload } from "../source-definitions";18} from "./google-discovery";
11import { runtimeEffectError } from "../../effect-errors";19import {
20 snapshotFromSourceCatalogSyncResult,
21} from "../catalog-sync-result";
22import {
23 createSourceFromPayload,
24} from "../source-definitions";
25import {
26 runtimeEffectError,
27} from "../../effect-errors";
12 28
13const fetchLiveDiscoveryDocument = async (): Promise<string> => {29const fetchLiveDiscoveryDocument = async (): Promise<string> => {
14 const response = await fetch("https://www.googleapis.com/discovery/v1/apis/sheets/v4/rest", {30 const response = await fetch("https://www.googleapis.com/discovery/v1/apis/sheets/v4/rest", {
@@ -66,7 +82,7 @@ describe("google discovery source adapter", () => { · 51 unmodified lines
66 await withStaticServer(discoveryDocument, async (discoveryUrl) => {82 await withStaticServer(discoveryDocument, async (discoveryUrl) => {
67 const source = await Effect.runPromise(83 const source = await Effect.runPromise(
68 createSourceFromPayload({84 createSourceFromPayload({
69 workspaceId: "ws_test" as any,85 scopeId: "ws_test" as any,
70 sourceId: SourceIdSchema.make(`src_${crypto.randomUUID()}`),86 sourceId: SourceIdSchema.make(`src_${crypto.randomUUID()}`),
71 payload: {87 payload: {
72 name: "Google Sheets",88 name: "Google Sheets",
packages/platform/sdk/src/runtime/sources/source-adapters/graphql.test.ts+24−8
@@ -1,13 +1,29 @@
1import { createServer } from "node:http";1import {
2 createServer,
3} from "node:http";
2 4
3import { describe, expect, it } from "@effect/vitest";5import {
4import { SourceIdSchema } from "#schema";6 describe,
7 expect,
8 it,
9} from "@effect/vitest";
10import {
11 SourceIdSchema,
12} from "#schema";
5import * as Effect from "effect/Effect";13import * as Effect from "effect/Effect";
6import { vi } from "vitest";14import {
15 vi,
16} from "vitest";
7 17
8import { createSourceFromPayload } from "../source-definitions";18import {
9import { graphqlSourceAdapter } from "./graphql";19 createSourceFromPayload,
10import { runtimeEffectError } from "../../effect-errors";20} from "../source-definitions";
21import {
22 graphqlSourceAdapter,
23} from "./graphql";
24import {
25 runtimeEffectError,
26} from "../../effect-errors";
11 27
12const toError = (cause: unknown): Error =>28const toError = (cause: unknown): Error =>
13 cause instanceof Error ? cause : new Error(String(cause));29 cause instanceof Error ? cause : new Error(String(cause));
@@ -92,7 +108,7 @@ describe("graphql source adapter", () => { · 78 unmodified lines
92 );108 );
93 109
94 const source = yield* createSourceFromPayload({110 const source = yield* createSourceFromPayload({
95 workspaceId: "ws_test" as any,111 scopeId: "ws_test" as any,
96 sourceId: SourceIdSchema.make(`src_${crypto.randomUUID()}`),112 sourceId: SourceIdSchema.make(`src_${crypto.randomUUID()}`),
97 payload: {113 payload: {
98 name: "GraphQL Timeout",114 name: "GraphQL Timeout",
packages/platform/sdk/src/runtime/sources/source-adapters/index.ts+6−2
@@ -4,9 +4,13 @@ import { · 3 unmodified lines
4 createSourceAdapterComposition,4 createSourceAdapterComposition,
5 type SourceAdapter,5 type SourceAdapter,
6} from "@executor/source-core";6} from "@executor/source-core";
7import { externalSourceAdapters } from "@executor/source-builtins";7import {
8 externalSourceAdapters,
9} from "@executor/source-builtins";
8 10
9import { internalSourceAdapter } from "./internal";11import {
12 internalSourceAdapter,
13} from "./internal";
10 14
11export type * from "@executor/source-core";15export type * from "@executor/source-core";
12 16
packages/platform/sdk/src/runtime/sources/source-adapters/internal.ts+3−1
@@ -10,7 +10,9 @@ import { · 9 unmodified lines
10 type Source,10 type Source,
11 type SourceAdapter,11 type SourceAdapter,
12} from "@executor/source-core";12} from "@executor/source-core";
13import { runtimeEffectError } from "../../effect-errors";13import {
14 runtimeEffectError,
15} from "../../effect-errors";
14 16
15const InternalBindingConfigSchema = Schema.Struct({});17const InternalBindingConfigSchema = Schema.Struct({});
16 18
packages/platform/sdk/src/runtime/sources/source-adapters/mcp.test.ts+73−34
@@ -1,10 +1,24 @@
1import { randomUUID } from "node:crypto";1import {
2 randomUUID,
3} from "node:crypto";
2 4
3import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";5import {
4import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";6 createMcpExpressApp,
5import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";7} from "@modelcontextprotocol/sdk/server/express.js";
6import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";8import type {
7import { describe, expect, it } from "@effect/vitest";9 OAuthClientProvider,
10} from "@modelcontextprotocol/sdk/client/auth.js";
11import {
12 McpServer,
13} from "@modelcontextprotocol/sdk/server/mcp.js";
14import {
15 StreamableHTTPServerTransport,
16} from "@modelcontextprotocol/sdk/server/streamableHttp.js";
17import {
18 describe,
19 expect,
20 it,
21} from "@effect/vitest";
8import {22import {
9 SourceCatalogIdSchema,23 SourceCatalogIdSchema,
10 SourceCatalogRevisionIdSchema,24 SourceCatalogRevisionIdSchema,
@@ -17,20 +31,39 @@ import type { · 6 unmodified lines
17} from "#schema";31} from "#schema";
18 32
19import * as Effect from "effect/Effect";33import * as Effect from "effect/Effect";
20import { z } from "zod/v4";34import {
35 z,
36} from "zod/v4";
21 37
22import { projectCatalogForAgentSdk } from "@executor/ir/catalog";38import {
23import type { CatalogSnapshotV1 } from "@executor/ir/model";39 projectCatalogForAgentSdk,
24import { createCatalogTypeProjector, projectedCatalogTypeRoots } from "../../catalog/catalog-typescript";40} from "@executor/ir/catalog";
25import { invokeIrTool } from "../../execution/ir-execution";41import type {
42 CatalogSnapshotV1,
43} from "@executor/ir/model";
44import {
45 createCatalogTypeProjector,
46 projectedCatalogTypeRoots,
47} from "../../catalog/catalog-typescript";
48import {
49 invokeIrTool,
50} from "../../execution/ir-execution";
26import {51import {
27 expandCatalogToolByPath,52 expandCatalogToolByPath,
28 type LoadedSourceCatalog,53 type LoadedSourceCatalog,
29} from "../../catalog/source/runtime";54} from "../../catalog/source/runtime";
30import { snapshotFromSourceCatalogSyncResult } from "../catalog-sync-result";55import {
31import { createSourceFromPayload } from "../source-definitions";56 snapshotFromSourceCatalogSyncResult,
32import { mcpSourceAdapter } from "./mcp";57} from "../catalog-sync-result";
33import { runtimeEffectError } from "../../effect-errors";58import {
59 createSourceFromPayload,
60} from "../source-definitions";
61import {
62 mcpSourceAdapter,
63} from "./mcp";
64import {
65 runtimeEffectError,
66} from "../../effect-errors";
34 67
35type RealMcpServer = {68type RealMcpServer = {
36 endpoint: string;69 endpoint: string;
@@ -47,7 +80,7 @@ const makeLoadedCatalog = (input: { · 10 unmodified lines
47 );80 );
48 const sourceRecord = {81 const sourceRecord = {
49 id: input.source.id,82 id: input.source.id,
50 workspaceId: input.source.workspaceId,83 scopeId: input.source.scopeId,
51 catalogId,84 catalogId,
52 catalogRevisionId: revisionId,85 catalogRevisionId: revisionId,
53 name: input.source.name,86 name: input.source.name,
@@ -535,9 +568,15 @@ const makeStatefulMcpServer = Effect.acquireRelease( · 481 unmodified lines
535);568);
536 569
537const STDIO_TEST_SERVER_SCRIPT = `570const STDIO_TEST_SERVER_SCRIPT = `
538import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";571import {
539import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";572 McpServer,
540import { z } from "zod/v4";573} from "@modelcontextprotocol/sdk/server/mcp.js";
574import {
575 StdioServerTransport,
576} from "@modelcontextprotocol/sdk/server/stdio.js";
577import {
578 z,
579} from "zod/v4";
541 580
542const server = new McpServer(581const server = new McpServer(
543 { name: "mcp-stdio-test-server", version: "1.0.0" },582 { name: "mcp-stdio-test-server", version: "1.0.0" },
@@ -594,7 +633,7 @@ describe("mcp source adapter", () => { · 50 unmodified lines
594 Effect.gen(function* () {633 Effect.gen(function* () {
595 const realServer = yield* makeRealMcpServer;634 const realServer = yield* makeRealMcpServer;
596 const source = yield* createSourceFromPayload({635 const source = yield* createSourceFromPayload({
597 workspaceId: "ws_test" as any,636 scopeId: "ws_test" as any,
598 sourceId: SourceIdSchema.make(`src_${randomUUID()}`),637 sourceId: SourceIdSchema.make(`src_${randomUUID()}`),
599 payload: {638 payload: {
600 name: "MCP Demo",639 name: "MCP Demo",
@@ -682,7 +721,7 @@ describe("mcp source adapter", () => { · 81 unmodified lines
682 Effect.gen(function* () {721 Effect.gen(function* () {
683 const realServer = yield* makeRealMcpServer;722 const realServer = yield* makeRealMcpServer;
684 const source = yield* createSourceFromPayload({723 const source = yield* createSourceFromPayload({
685 workspaceId: "ws_test" as any,724 scopeId: "ws_test" as any,
686 sourceId: SourceIdSchema.make(`src_${randomUUID()}`),725 sourceId: SourceIdSchema.make(`src_${randomUUID()}`),
687 payload: {726 payload: {
688 name: "MCP Demo",727 name: "MCP Demo",
@@ -733,8 +772,8 @@ describe("mcp source adapter", () => { · 44 unmodified lines
733 }772 }
734 773
735 const result = yield* invokeIrTool({774 const result = yield* invokeIrTool({
736 workspaceId: source.workspaceId,775 scopeId: source.scopeId,
737 accountId: "acct_test" as any,776 actorScopeId: "acct_test" as any,
738 tool,777 tool,
739 auth: {778 auth: {
740 placements: [],779 placements: [],
@@ -769,7 +808,7 @@ describe("mcp source adapter", () => { · 28 unmodified lines
769 const realServer = yield* makeAuthenticatedMcpServer("Bearer mcp-auth-token");808 const realServer = yield* makeAuthenticatedMcpServer("Bearer mcp-auth-token");
770 const authProvider = makeStaticAuthProvider("mcp-auth-token");809 const authProvider = makeStaticAuthProvider("mcp-auth-token");
771 const source = yield* createSourceFromPayload({810 const source = yield* createSourceFromPayload({
772 workspaceId: "ws_test" as any,811 scopeId: "ws_test" as any,
773 sourceId: SourceIdSchema.make(`src_${randomUUID()}`),812 sourceId: SourceIdSchema.make(`src_${randomUUID()}`),
774 payload: {813 payload: {
775 name: "Authenticated MCP Demo",814 name: "Authenticated MCP Demo",
@@ -822,8 +861,8 @@ describe("mcp source adapter", () => { · 46 unmodified lines
822 }861 }
823 862
824 const result = yield* invokeIrTool({863 const result = yield* invokeIrTool({
825 workspaceId: source.workspaceId,864 scopeId: source.scopeId,
826 accountId: "acct_test" as any,865 actorScopeId: "acct_test" as any,
827 tool,866 tool,
828 auth: resolvedAuth,867 auth: resolvedAuth,
829 args: {868 args: {
@@ -849,7 +888,7 @@ describe("mcp source adapter", () => { · 19 unmodified lines
849 Effect.gen(function* () {888 Effect.gen(function* () {
850 const realServer = yield* makeStatefulMcpServer;889 const realServer = yield* makeStatefulMcpServer;
851 const source = yield* createSourceFromPayload({890 const source = yield* createSourceFromPayload({
852 workspaceId: "ws_test" as any,891 scopeId: "ws_test" as any,
853 sourceId: SourceIdSchema.make(`src_${randomUUID()}`),892 sourceId: SourceIdSchema.make(`src_${randomUUID()}`),
854 payload: {893 payload: {
855 name: "Stateful MCP Demo",894 name: "Stateful MCP Demo",
@@ -905,8 +944,8 @@ describe("mcp source adapter", () => { · 49 unmodified lines
905 }944 }
906 945
907 const incrementResult = yield* invokeIrTool({946 const incrementResult = yield* invokeIrTool({
908 workspaceId: source.workspaceId,947 scopeId: source.scopeId,
909 accountId: "acct_test" as any,948 actorScopeId: "acct_test" as any,
910 tool: incrementTool,949 tool: incrementTool,
911 auth: {950 auth: {
912 placements: [],951 placements: [],
@@ -924,8 +963,8 @@ describe("mcp source adapter", () => { · 11 unmodified lines
924 });963 });
925 964
926 const readResult = yield* invokeIrTool({965 const readResult = yield* invokeIrTool({
927 workspaceId: source.workspaceId,966 scopeId: source.scopeId,
928 accountId: "acct_test" as any,967 actorScopeId: "acct_test" as any,
929 tool: readTool,968 tool: readTool,
930 auth: {969 auth: {
931 placements: [],970 placements: [],
@@ -970,7 +1009,7 @@ describe("mcp source adapter", () => { · 38 unmodified lines
970 it.scoped("supports stdio MCP sources", () =>1009 it.scoped("supports stdio MCP sources", () =>
971 Effect.gen(function* () {1010 Effect.gen(function* () {
972 const source = yield* createSourceFromPayload({1011 const source = yield* createSourceFromPayload({
973 workspaceId: "ws_test" as any,1012 scopeId: "ws_test" as any,
974 sourceId: SourceIdSchema.make(`src_${randomUUID()}`),1013 sourceId: SourceIdSchema.make(`src_${randomUUID()}`),
975 payload: {1014 payload: {
976 name: "Stdio MCP Demo",1015 name: "Stdio MCP Demo",
@@ -1025,8 +1064,8 @@ describe("mcp source adapter", () => { · 48 unmodified lines
1025 }1064 }
1026 1065
1027 const result = yield* invokeIrTool({1066 const result = yield* invokeIrTool({
1028 workspaceId: source.workspaceId,1067 scopeId: source.scopeId,
1029 accountId: "acct_test" as any,1068 actorScopeId: "acct_test" as any,
1030 tool,1069 tool,
1031 auth: {1070 auth: {
1032 placements: [],1071 placements: [],
packages/platform/sdk/src/runtime/sources/source-auth-service.ts+379−305
@@ -9,7 +9,6 @@ import { · 8 unmodified lines
9 type SourceAdapterOauth2SetupConfig,9 type SourceAdapterOauth2SetupConfig,
10} from "@executor/source-core";10} from "@executor/source-core";
11import {11import {
12 AccountId,
13 type CredentialSlot,12 type CredentialSlot,
14 McpSourceAuthSessionDataJsonSchema,13 McpSourceAuthSessionDataJsonSchema,
15 type McpSourceAuthSessionData,14 type McpSourceAuthSessionData,
@@ -30,12 +29,12 @@ import { · 14 unmodified lines
30 SourceSchema,29 SourceSchema,
31 type SecretRef,30 type SecretRef,
32 type StringMap,31 type StringMap,
33 WorkspaceOauthClientIdSchema,32 ScopeOauthClientIdSchema,
34 WorkspaceSourceOauthClientIdSchema,33 ScopedSourceOauthClientIdSchema,
35 WorkspaceSourceOauthClientMetadataJsonSchema,34 ScopedSourceOauthClientMetadataJsonSchema,
36 type WorkspaceOauthClient,35 type ScopeOauthClient,
37 type WorkspaceSourceOauthClientRedirectMode,36 type ScopedSourceOauthClientRedirectMode,
38 type WorkspaceId,37 type ScopeId,
39} from "#schema";38} from "#schema";
40import * as Context from "effect/Context";39import * as Context from "effect/Context";
41import * as Either from "effect/Either";40import * as Either from "effect/Either";
@@ -51,15 +50,17 @@ import { · 9 unmodified lines
51 type LiveExecutionManager,50 type LiveExecutionManager,
52} from "../execution/live";51} from "../execution/live";
53import {52import {
54 getRuntimeLocalWorkspaceOption,53 getRuntimeLocalScopeOption,
55 provideOptionalRuntimeLocalWorkspace,54 provideOptionalRuntimeLocalScope,
56 type RuntimeLocalWorkspaceState,55 type RuntimeLocalScopeState,
57} from "../workspace/runtime-context";56} from "../scope/runtime-context";
58import {57import {
59 exchangeMcpOAuthAuthorizationCode,58 exchangeMcpOAuthAuthorizationCode,
60 startMcpOAuthAuthorization,59 startMcpOAuthAuthorization,
61} from "../auth/mcp-oauth";60} from "../auth/mcp-oauth";
62import { createPersistedMcpOAuthSourceAuth } from "../auth/mcp-auth-provider";61import {
62 createPersistedMcpOAuthSourceAuth,
63} from "../auth/mcp-auth-provider";
63import {64import {
64 createSourceFromPayload,65 createSourceFromPayload,
65 updateSourceFromPayload,66 updateSourceFromPayload,
@@ -79,7 +80,7 @@ import { · 13 unmodified lines
79 SecretMaterialResolverService,80 SecretMaterialResolverService,
80 SecretMaterialStorerService,81 SecretMaterialStorerService,
81 type StoreSecretMaterial,82 type StoreSecretMaterial,
82} from "../workspace/secret-material-providers";83} from "../scope/secret-material-providers";
83import {84import {
84 removeAuthLeaseAndSecrets,85 removeAuthLeaseAndSecrets,
85 upsertOauth2AuthorizedUserLeaseFromTokenResponse,86 upsertOauth2AuthorizedUserLeaseFromTokenResponse,
@@ -97,14 +98,23 @@ import { · 11 unmodified lines
97 createPkceCodeVerifier,98 createPkceCodeVerifier,
98 exchangeOAuth2AuthorizationCode,99 exchangeOAuth2AuthorizationCode,
99} from "../auth/oauth2-pkce";100} from "../auth/oauth2-pkce";
100import { startOauthLoopbackRedirectServer } from "../auth/oauth-loopback";101import {
102 startOauthLoopbackRedirectServer,
103} from "../auth/oauth-loopback";
101import {104import {
102 type RuntimeSourceStore,105 type RuntimeSourceStore,
103 RuntimeSourceStoreService,106 RuntimeSourceStoreService,
104} from "./source-store";107} from "./source-store";
105import type { WorkspaceStorageServices } from "../workspace/storage";108import type {
106import { ExecutorStateStore, type ExecutorStateStoreShape } from "../executor-state-store";109 ScopeStorageServices,
107import { runtimeEffectError } from "../effect-errors";110} from "../scope/storage";
111import {
112 ExecutorStateStore,
113 type ExecutorStateStoreShape,
114} from "../executor-state-store";
115import {
116 runtimeEffectError,
117} from "../effect-errors";
108 118
109const trimOrNull = (value: string | null | undefined): string | null => {119const trimOrNull = (value: string | null | undefined): string | null => {
110 if (value === null || value === undefined) {120 if (value === null || value === undefined) {
@@ -199,11 +209,11 @@ const normalizeMcpEndpoint = (input: { · 88 unmodified lines
199 209
200const resolveSourceCredentialOauthCompleteUrl = (input: {210const resolveSourceCredentialOauthCompleteUrl = (input: {
201 baseUrl: string;211 baseUrl: string;
202 workspaceId: WorkspaceId;212 scopeId: ScopeId;
203 sourceId: Source["id"];213 sourceId: Source["id"];
204}): string =>214}): string =>
205 new URL(215 new URL(
206 `/v1/workspaces/${encodeURIComponent(input.workspaceId)}/sources/${encodeURIComponent(input.sourceId)}/credentials/oauth/complete`,216 `/v1/workspaces/${encodeURIComponent(input.scopeId)}/sources/${encodeURIComponent(input.sourceId)}/credentials/oauth/complete`,
207 input.baseUrl,217 input.baseUrl,
208 ).toString();218 ).toString();
209 219
@@ -217,10 +227,10 @@ const resolveSourceOAuthCallbackUrl = (input: { · 7 unmodified lines
217 227
218const resolveWorkspaceProviderOauthCompleteUrl = (input: {228const resolveWorkspaceProviderOauthCompleteUrl = (input: {
219 baseUrl: string;229 baseUrl: string;
220 workspaceId: WorkspaceId;230 scopeId: ScopeId;
221}): string =>231}): string =>
222 new URL(232 new URL(
223 `/v1/workspaces/${encodeURIComponent(input.workspaceId)}/oauth/provider/callback`,233 `/v1/workspaces/${encodeURIComponent(input.scopeId)}/oauth/provider/callback`,
224 input.baseUrl,234 input.baseUrl,
225 ).toString();235 ).toString();
226 236
@@ -491,7 +501,7 @@ const serializeJson = (value: unknown): string | null => { · 264 unmodified lines
491};501};
492 502
493const updateSourceStatus = Effect.fn("source.status.update")((sourceStore: RuntimeSourceStore, source: Source, input: {503const updateSourceStatus = Effect.fn("source.status.update")((sourceStore: RuntimeSourceStore, source: Source, input: {
494 actorAccountId?: AccountId | null;504 actorScopeId?: ScopeId | null;
495 status: Source["status"];505 status: Source["status"];
496 lastError?: string | null;506 lastError?: string | null;
497 auth?: Source["auth"];507 auth?: Source["auth"];
@@ -499,9 +509,9 @@ const updateSourceStatus = Effect.fn("source.status.update")((sourceStore: Runti · 1 unmodified line
499}) =>509}) =>
500 Effect.gen(function* () {510 Effect.gen(function* () {
501 const latest = yield* sourceStore.loadSourceById({511 const latest = yield* sourceStore.loadSourceById({
502 workspaceId: source.workspaceId,512 scopeId: source.scopeId,
503 sourceId: source.id,513 sourceId: source.id,
504 actorAccountId: input.actorAccountId,514 actorScopeId: input.actorScopeId,
505 });515 });
506 516
507 return yield* sourceStore.persistSource({517 return yield* sourceStore.persistSource({
@@ -512,7 +522,7 @@ const updateSourceStatus = Effect.fn("source.status.update")((sourceStore: Runti · 4 unmodified lines
512 importAuth: input.importAuth ?? latest.importAuth,522 importAuth: input.importAuth ?? latest.importAuth,
513 updatedAt: Date.now(),523 updatedAt: Date.now(),
514 }, {524 }, {
515 actorAccountId: input.actorAccountId,525 actorScopeId: input.actorScopeId,
516 });526 });
517 }).pipe(527 }).pipe(
518 Effect.withSpan("source.status.update", {528 Effect.withSpan("source.status.update", {
@@ -564,8 +574,8 @@ export type ExecutorHttpSourceAuthInput = · 45 unmodified lines
564export type ExecutorAddSourceInput =574export type ExecutorAddSourceInput =
565 | {575 | {
566 kind?: "mcp";576 kind?: "mcp";
567 workspaceId: WorkspaceId;577 scopeId: ScopeId;
568 actorAccountId?: AccountId | null;578 actorScopeId?: ScopeId | null;
569 executionId: SourceAuthSession["executionId"];579 executionId: SourceAuthSession["executionId"];
570 interactionId: SourceAuthSession["interactionId"];580 interactionId: SourceAuthSession["interactionId"];
571 endpoint?: string | null;581 endpoint?: string | null;
@@ -581,8 +591,8 @@ export type ExecutorAddSourceInput = · 9 unmodified lines
581 }591 }
582 | {592 | {
583 kind: "openapi";593 kind: "openapi";
584 workspaceId: WorkspaceId;594 scopeId: ScopeId;
585 actorAccountId?: AccountId | null;595 actorScopeId?: ScopeId | null;
586 executionId: SourceAuthSession["executionId"];596 executionId: SourceAuthSession["executionId"];
587 interactionId: SourceAuthSession["interactionId"];597 interactionId: SourceAuthSession["interactionId"];
588 endpoint: string;598 endpoint: string;
@@ -595,8 +605,8 @@ export type ExecutorAddSourceInput = · 6 unmodified lines
595 }605 }
596 | {606 | {
597 kind: "graphql";607 kind: "graphql";
598 workspaceId: WorkspaceId;608 scopeId: ScopeId;
599 actorAccountId?: AccountId | null;609 actorScopeId?: ScopeId | null;
600 executionId: SourceAuthSession["executionId"];610 executionId: SourceAuthSession["executionId"];
601 interactionId: SourceAuthSession["interactionId"];611 interactionId: SourceAuthSession["interactionId"];
602 endpoint: string;612 endpoint: string;
@@ -608,15 +618,15 @@ export type ExecutorAddSourceInput = · 5 unmodified lines
608 }618 }
609 | {619 | {
610 kind: "google_discovery";620 kind: "google_discovery";
611 workspaceId: WorkspaceId;621 scopeId: ScopeId;
612 actorAccountId?: AccountId | null;622 actorScopeId?: ScopeId | null;
613 executionId: SourceAuthSession["executionId"];623 executionId: SourceAuthSession["executionId"];
614 interactionId: SourceAuthSession["interactionId"];624 interactionId: SourceAuthSession["interactionId"];
615 service: string;625 service: string;
616 version: string;626 version: string;
617 discoveryUrl?: string | null;627 discoveryUrl?: string | null;
618 scopes?: ReadonlyArray<string> | null;628 scopes?: ReadonlyArray<string> | null;
619 workspaceOauthClientId?: WorkspaceOauthClient["id"] | null;629 scopeOauthClientId?: ScopeOauthClient["id"] | null;
620 oauthClient?: SourceOauthClientInput | null;630 oauthClient?: SourceOauthClientInput | null;
621 name?: string | null;631 name?: string | null;
622 namespace?: string | null;632 namespace?: string | null;
@@ -641,8 +651,8 @@ export type ExecutorMcpSourceInput = Exclude< · 18 unmodified lines
641>;651>;
642 652
643export type ConnectMcpSourceInput = {653export type ConnectMcpSourceInput = {
644 workspaceId: WorkspaceId;654 scopeId: ScopeId;
645 actorAccountId?: AccountId | null;655 actorScopeId?: ScopeId | null;
646 sourceId?: Source["id"] | null;656 sourceId?: Source["id"] | null;
647 endpoint?: string | null;657 endpoint?: string | null;
648 name?: string | null;658 name?: string | null;
@@ -659,11 +669,11 @@ export type ConnectMcpSourceInput = { · 10 unmodified lines
659};669};
660 670
661export type ConnectGoogleDiscoveryBatchInput = {671export type ConnectGoogleDiscoveryBatchInput = {
662 workspaceId: WorkspaceId;672 scopeId: ScopeId;
663 actorAccountId?: AccountId | null;673 actorScopeId?: ScopeId | null;
664 executionId: SourceAuthSession["executionId"];674 executionId: SourceAuthSession["executionId"];
665 interactionId: SourceAuthSession["interactionId"];675 interactionId: SourceAuthSession["interactionId"];
666 workspaceOauthClientId: WorkspaceOauthClient["id"];676 scopeOauthClientId: ScopeOauthClient["id"];
667 sources: ReadonlyArray<{677 sources: ReadonlyArray<{
668 service: string;678 service: string;
669 version: string;679 version: string;
@@ -687,8 +697,8 @@ export type ConnectGoogleDiscoveryBatchResult = { · 17 unmodified lines
687 } | null;697 } | null;
688};698};
689 699
690export type CreateWorkspaceOauthClientInput = {700export type CreateScopeOauthClientInput = {
691 workspaceId: WorkspaceId;701 scopeId: ScopeId;
692 providerKey: string;702 providerKey: string;
693 label?: string | null;703 label?: string | null;
694 oauthClient: SourceOauthClientInput;704 oauthClient: SourceOauthClientInput;
@@ -707,8 +717,8 @@ export type SourceOAuthProviderInput = { · 12 unmodified lines
707};717};
708 718
709export type StartSourceOAuthSessionInput = {719export type StartSourceOAuthSessionInput = {
710 workspaceId: WorkspaceId;720 scopeId: ScopeId;
711 actorAccountId?: AccountId | null;721 actorScopeId?: ScopeId | null;
712 provider: SourceOAuthProviderInput;722 provider: SourceOAuthProviderInput;
713 baseUrl?: string | null;723 baseUrl?: string | null;
714 displayName?: string | null;724 displayName?: string | null;
@@ -928,11 +938,11 @@ type ResolvedSourceOauthClient = { · 213 unmodified lines
928 providerKey: string;938 providerKey: string;
929 clientId: string;939 clientId: string;
930 clientSecret: SecretRef | null;940 clientSecret: SecretRef | null;
931 redirectMode: WorkspaceSourceOauthClientRedirectMode;941 redirectMode: ScopedSourceOauthClientRedirectMode;
932};942};
933 943
934type ResolvedWorkspaceOauthClient = ResolvedSourceOauthClient & {944type ResolvedScopeOauthClient = ResolvedSourceOauthClient & {
935 id: WorkspaceOauthClient["id"];945 id: ScopeOauthClient["id"];
936 label: string | null;946 label: string | null;
937};947};
938 948
@@ -954,22 +964,22 @@ type RuntimeProviderAuthResult = · 15 unmodified lines
954 authorizationUrl: string;964 authorizationUrl: string;
955 };965 };
956 966
957const decodeWorkspaceSourceOauthClientMetadataOption = Schema.decodeUnknownOption(967const decodeScopedSourceOauthClientMetadataOption = Schema.decodeUnknownOption(
958 WorkspaceSourceOauthClientMetadataJsonSchema,968 ScopedSourceOauthClientMetadataJsonSchema,
959);969);
960 970
961const encodeWorkspaceSourceOauthClientMetadataJson = Schema.encodeSync(971const encodeScopedSourceOauthClientMetadataJson = Schema.encodeSync(
962 WorkspaceSourceOauthClientMetadataJsonSchema,972 ScopedSourceOauthClientMetadataJsonSchema,
963);973);
964 974
965const sourceOauthClientRedirectMode = (client: {975const sourceOauthClientRedirectMode = (client: {
966 clientMetadataJson: string | null;976 clientMetadataJson: string | null;
967}): WorkspaceSourceOauthClientRedirectMode => {977}): ScopedSourceOauthClientRedirectMode => {
968 if (client.clientMetadataJson === null) {978 if (client.clientMetadataJson === null) {
969 return "app_callback";979 return "app_callback";
970 }980 }
971 981
972 const decoded = decodeWorkspaceSourceOauthClientMetadataOption(982 const decoded = decodeScopedSourceOauthClientMetadataOption(
973 client.clientMetadataJson,983 client.clientMetadataJson,
974 );984 );
975 if (Option.isNone(decoded)) {985 if (Option.isNone(decoded)) {
@@ -1009,8 +1019,8 @@ const upsertSourceOauthClient = (input: { · 33 unmodified lines
1009 return yield* runtimeEffectError("sources/source-auth-service", `Source ${input.source.id} does not support OAuth client configuration`);1019 return yield* runtimeEffectError("sources/source-auth-service", `Source ${input.source.id} does not support OAuth client configuration`);
1010 }1020 }
1011 1021
1012 const existing = yield* input.executorState.sourceOauthClients.getByWorkspaceSourceAndProvider({1022 const existing = yield* input.executorState.sourceOauthClients.getByScopeSourceAndProvider({
1013 workspaceId: input.source.workspaceId,1023 scopeId: input.source.scopeId,
1014 sourceId: input.source.id,1024 sourceId: input.source.id,
1015 providerKey: setupConfig.providerKey,1025 providerKey: setupConfig.providerKey,
1016 });1026 });
@@ -1029,18 +1039,18 @@ const upsertSourceOauthClient = (input: { · 12 unmodified lines
1029 const now = Date.now();1039 const now = Date.now();
1030 const clientId = Option.isSome(existing)1040 const clientId = Option.isSome(existing)
1031 ? existing.value.id1041 ? existing.value.id
1032 : WorkspaceSourceOauthClientIdSchema.make(1042 : ScopedSourceOauthClientIdSchema.make(
1033 `src_oauth_client_${crypto.randomUUID()}`,1043 `src_oauth_client_${crypto.randomUUID()}`,
1034 );1044 );
1035 yield* input.executorState.sourceOauthClients.upsert({1045 yield* input.executorState.sourceOauthClients.upsert({
1036 id: clientId,1046 id: clientId,
1037 workspaceId: input.source.workspaceId,1047 scopeId: input.source.scopeId,
1038 sourceId: input.source.id,1048 sourceId: input.source.id,
1039 providerKey: setupConfig.providerKey,1049 providerKey: setupConfig.providerKey,
1040 clientId: normalizedOauthClient.clientId,1050 clientId: normalizedOauthClient.clientId,
1041 clientSecretProviderId: clientSecretRef?.providerId ?? null,1051 clientSecretProviderId: clientSecretRef?.providerId ?? null,
1042 clientSecretHandle: clientSecretRef?.handle ?? null,1052 clientSecretHandle: clientSecretRef?.handle ?? null,
1043 clientMetadataJson: encodeWorkspaceSourceOauthClientMetadataJson({1053 clientMetadataJson: encodeScopedSourceOauthClientMetadataJson({
1044 redirectMode: normalizedOauthClient.redirectMode ?? "app_callback",1054 redirectMode: normalizedOauthClient.redirectMode ?? "app_callback",
1045 }),1055 }),
1046 createdAt: Option.isSome(existing) ? existing.value.createdAt : now,1056 createdAt: Option.isSome(existing) ? existing.value.createdAt : now,
@@ -1084,8 +1094,8 @@ const resolveExistingSourceOauthClient = (input: { · 37 unmodified lines
1084 return null;1094 return null;
1085 }1095 }
1086 1096
1087 const existing = yield* input.executorState.sourceOauthClients.getByWorkspaceSourceAndProvider({1097 const existing = yield* input.executorState.sourceOauthClients.getByScopeSourceAndProvider({
1088 workspaceId: input.source.workspaceId,1098 scopeId: input.source.scopeId,
1089 sourceId: input.source.id,1099 sourceId: input.source.id,
1090 providerKey: setupConfig.providerKey,1100 providerKey: setupConfig.providerKey,
1091 });1101 });
@@ -1101,9 +1111,9 @@ const resolveExistingSourceOauthClient = (input: { · 9 unmodified lines
1101 };1111 };
1102 });1112 });
1103 1113
1104const createWorkspaceOauthClient = (input: {1114const createScopeOauthClient = (input: {
1105 executorState: ExecutorStateStoreShape;1115 executorState: ExecutorStateStoreShape;
1106 workspaceId: WorkspaceId;1116 scopeId: ScopeId;
1107 providerKey: string;1117 providerKey: string;
1108 oauthClient: SourceOauthClientInput;1118 oauthClient: SourceOauthClientInput;
1109 label?: string | null;1119 label?: string | null;
@@ -1111,7 +1121,7 @@ const createWorkspaceOauthClient = (input: { · 1 unmodified line
1111 input: SourceOauthClientInput,1121 input: SourceOauthClientInput,
1112 ) => Effect.Effect<SourceOauthClientInput, Error, never>;1122 ) => Effect.Effect<SourceOauthClientInput, Error, never>;
1113 storeSecretMaterial: StoreSecretMaterial;1123 storeSecretMaterial: StoreSecretMaterial;
1114}): Effect.Effect<ResolvedWorkspaceOauthClient, Error, never> =>1124}): Effect.Effect<ResolvedScopeOauthClient, Error, never> =>
1115 Effect.gen(function* () {1125 Effect.gen(function* () {
1116 const normalizedOauthClient = input.normalizeOauthClient1126 const normalizedOauthClient = input.normalizeOauthClient
1117 ? yield* input.normalizeOauthClient(input.oauthClient)1127 ? yield* input.normalizeOauthClient(input.oauthClient)
@@ -1123,19 +1133,19 @@ const createWorkspaceOauthClient = (input: { · 5 unmodified lines
1123 })1133 })
1124 : null;1134 : null;
1125 const now = Date.now();1135 const now = Date.now();
1126 const id = WorkspaceOauthClientIdSchema.make(1136 const id = ScopeOauthClientIdSchema.make(
1127 `ws_oauth_client_${crypto.randomUUID()}`,1137 `ws_oauth_client_${crypto.randomUUID()}`,
1128 );1138 );
1129 1139
1130 yield* input.executorState.workspaceOauthClients.upsert({1140 yield* input.executorState.scopeOauthClients.upsert({
1131 id,1141 id,
1132 workspaceId: input.workspaceId,1142 scopeId: input.scopeId,
1133 providerKey: input.providerKey,1143 providerKey: input.providerKey,
1134 label: trimOrNull(input.label) ?? null,1144 label: trimOrNull(input.label) ?? null,
1135 clientId: normalizedOauthClient.clientId,1145 clientId: normalizedOauthClient.clientId,
1136 clientSecretProviderId: clientSecretRef?.providerId ?? null,1146 clientSecretProviderId: clientSecretRef?.providerId ?? null,
1137 clientSecretHandle: clientSecretRef?.handle ?? null,1147 clientSecretHandle: clientSecretRef?.handle ?? null,
1138 clientMetadataJson: encodeWorkspaceSourceOauthClientMetadataJson({1148 clientMetadataJson: encodeScopedSourceOauthClientMetadataJson({
1139 redirectMode: normalizedOauthClient.redirectMode ?? "app_callback",1149 redirectMode: normalizedOauthClient.redirectMode ?? "app_callback",
1140 }),1150 }),
1141 createdAt: now,1151 createdAt: now,
@@ -1152,23 +1162,28 @@ const createWorkspaceOauthClient = (input: { · 10 unmodified lines
1152 };1162 };
1153 });1163 });
1154 1164
1155const resolveWorkspaceOauthClientById = (input: {1165const resolveScopeOauthClientById = (input: {
1156 executorState: ExecutorStateStoreShape;1166 executorState: ExecutorStateStoreShape;
1157 workspaceId: WorkspaceId;1167 scopeId: ScopeId;
1158 oauthClientId: WorkspaceOauthClient["id"];1168 oauthClientId: ScopeOauthClient["id"];
1159 providerKey: string;1169 providerKey: string;
1160}): Effect.Effect<ResolvedWorkspaceOauthClient | null, Error, never> =>1170 localScopeState?: RuntimeLocalScopeState;
1171}): Effect.Effect<ResolvedScopeOauthClient | null, Error, never> =>
1161 Effect.gen(function* () {1172 Effect.gen(function* () {
1162 const existing = yield* input.executorState.workspaceOauthClients.getById(input.oauthClientId);1173 const existing = yield* input.executorState.scopeOauthClients.getById(input.oauthClientId);
1163 if (Option.isNone(existing)) {1174 if (Option.isNone(existing)) {
1164 return null;1175 return null;
1165 }1176 }
1166 1177
1178 const allowedScopeIds = resolvedScopeSearchOrder({
1179 scopeId: input.scopeId,
1180 localScopeState: input.localScopeState,
1181 });
1167 if (1182 if (
1168 existing.value.workspaceId !== input.workspaceId1183 !allowedScopeIds.includes(existing.value.scopeId)
1169 || existing.value.providerKey !== input.providerKey1184 || existing.value.providerKey !== input.providerKey
1170 ) {1185 ) {
1171 return yield* runtimeEffectError("sources/source-auth-service", `Workspace OAuth client ${input.oauthClientId} is not valid for ${input.providerKey}`);1186 return yield* runtimeEffectError("sources/source-auth-service", `Scope OAuth client ${input.oauthClientId} is not valid for ${input.providerKey}`);
1172 }1187 }
1173 1188
1174 return {1189 return {
@@ -1192,6 +1207,25 @@ const providerGrantCoversScopes = ( · 17 unmodified lines
1192const normalizeScopes = (scopes: ReadonlyArray<string>): ReadonlyArray<string> =>1207const normalizeScopes = (scopes: ReadonlyArray<string>): ReadonlyArray<string> =>
1193 [...new Set(scopes.map((scope) => scope.trim()).filter((scope) => scope.length > 0))];1208 [...new Set(scopes.map((scope) => scope.trim()).filter((scope) => scope.length > 0))];
1194 1209
1210const resolvedScopeSearchOrder = (input: {
1211 scopeId: ScopeId;
1212 localScopeState?: RuntimeLocalScopeState;
1213}): ReadonlyArray<ScopeId> => {
1214 const localScopeState = input.localScopeState;
1215 if (!localScopeState) {
1216 return [input.scopeId];
1217 }
1218
1219 if (localScopeState.installation.scopeId !== input.scopeId) {
1220 return [input.scopeId];
1221 }
1222
1223 return [...new Set([
1224 input.scopeId,
1225 ...localScopeState.installation.resolutionScopeIds,
1226 ])];
1227};
1228
1195const authorizationParamsMatch = (1229const authorizationParamsMatch = (
1196 left: SourceAdapterOauth2SetupConfig["authorizationParams"],1230 left: SourceAdapterOauth2SetupConfig["authorizationParams"],
1197 right: SourceAdapterOauth2SetupConfig["authorizationParams"],1231 right: SourceAdapterOauth2SetupConfig["authorizationParams"],
@@ -1272,34 +1306,44 @@ const mergeRuntimeProviderAuthSetupConfig = ( · 74 unmodified lines
1272 1306
1273const findReusableProviderGrant = (input: {1307const findReusableProviderGrant = (input: {
1274 executorState: ExecutorStateStoreShape;1308 executorState: ExecutorStateStoreShape;
1275 workspaceId: WorkspaceId;1309 scopeId: ScopeId;
1276 actorAccountId?: AccountId | null;1310 actorScopeId?: ScopeId | null;
1277 providerKey: string;1311 providerKey: string;
1278 oauthClientId: WorkspaceOauthClient["id"];1312 oauthClientId: ScopeOauthClient["id"];
1279 requiredScopes: ReadonlyArray<string>;1313 requiredScopes: ReadonlyArray<string>;
1314 localScopeState?: RuntimeLocalScopeState;
1280}): Effect.Effect<import("effect/Option").Option<import("#schema").ProviderAuthGrant>, Error, never> =>1315}): Effect.Effect<import("effect/Option").Option<import("#schema").ProviderAuthGrant>, Error, never> =>
1281 Effect.map(1316 Effect.gen(function* () {
1282 input.executorState.providerAuthGrants.listByWorkspaceActorAndProvider({1317 const scopeIds = resolvedScopeSearchOrder({
1283 workspaceId: input.workspaceId,1318 scopeId: input.scopeId,
1284 actorAccountId: input.actorAccountId ?? null,1319 localScopeState: input.localScopeState,
1285 providerKey: input.providerKey,1320 });
1286 }),1321
1287 (grants) =>1322 for (const candidateScopeId of scopeIds) {
1288 Option.fromNullable(1323 const grants = yield* input.executorState.providerAuthGrants.listByScopeActorAndProvider({
1289 grants.find(1324 scopeId: candidateScopeId,
1290 (grant) =>1325 actorScopeId: input.actorScopeId ?? null,
1291 grant.oauthClientId === input.oauthClientId1326 providerKey: input.providerKey,
1292 && providerGrantCoversScopes(grant.grantedScopes, input.requiredScopes),1327 });
1293 ),1328 const matchingGrant = grants.find(
1294 ),1329 (grant) =>
1295 );1330 grant.oauthClientId === input.oauthClientId
1331 && providerGrantCoversScopes(grant.grantedScopes, input.requiredScopes),
1332 );
1333 if (matchingGrant) {
1334 return Option.some(matchingGrant);
1335 }
1336 }
1337
1338 return Option.none();
1339 });
1296 1340
1297const upsertProviderAuthGrant = (input: {1341const upsertProviderAuthGrant = (input: {
1298 executorState: ExecutorStateStoreShape;1342 executorState: ExecutorStateStoreShape;
1299 workspaceId: WorkspaceId;1343 scopeId: ScopeId;
1300 actorAccountId?: AccountId | null;1344 actorScopeId?: ScopeId | null;
1301 providerKey: string;1345 providerKey: string;
1302 oauthClientId: WorkspaceOauthClient["id"];1346 oauthClientId: ScopeOauthClient["id"];
1303 tokenEndpoint: string;1347 tokenEndpoint: string;
1304 clientAuthentication: "none" | "client_secret_post";1348 clientAuthentication: "none" | "client_secret_post";
1305 headerName: string;1349 headerName: string;
@@ -1328,8 +1372,8 @@ const upsertProviderAuthGrant = (input: { · 22 unmodified lines
1328 const now = Date.now();1372 const now = Date.now();
1329 const nextGrant = {1373 const nextGrant = {
1330 id: existingGrant?.id ?? ProviderAuthGrantIdSchema.make(`provider_grant_${crypto.randomUUID()}`),1374 id: existingGrant?.id ?? ProviderAuthGrantIdSchema.make(`provider_grant_${crypto.randomUUID()}`),
1331 workspaceId: input.workspaceId,1375 scopeId: input.scopeId,
1332 actorAccountId: input.actorAccountId ?? null,1376 actorScopeId: input.actorScopeId ?? null,
1333 providerKey: input.providerKey,1377 providerKey: input.providerKey,
1334 oauthClientId: input.oauthClientId,1378 oauthClientId: input.oauthClientId,
1335 tokenEndpoint: input.tokenEndpoint,1379 tokenEndpoint: input.tokenEndpoint,
@@ -1369,16 +1413,16 @@ const startOauth2PkceSourceCredentialSetup = (input: { · 33 unmodified lines
1369 executorState: ExecutorStateStoreShape;1413 executorState: ExecutorStateStoreShape;
1370 sourceStore: RuntimeSourceStore;1414 sourceStore: RuntimeSourceStore;
1371 source: Source;1415 source: Source;
1372 actorAccountId?: AccountId | null;1416 actorScopeId?: ScopeId | null;
1373 executionId?: SourceAuthSession["executionId"];1417 executionId?: SourceAuthSession["executionId"];
1374 interactionId?: SourceAuthSession["interactionId"];1418 interactionId?: SourceAuthSession["interactionId"];
1375 baseUrl: string;1419 baseUrl: string;
1376 redirectModeOverride?: WorkspaceSourceOauthClientRedirectMode;1420 redirectModeOverride?: ScopedSourceOauthClientRedirectMode;
1377 storeSecretMaterial: StoreSecretMaterial;1421 storeSecretMaterial: StoreSecretMaterial;
1378}): Effect.Effect<1422}): Effect.Effect<
1379 Extract<ExecutorSourceAddResult, { kind: "oauth_required" }> | null,1423 Extract<ExecutorSourceAddResult, { kind: "oauth_required" }> | null,
1380 Error,1424 Error,
1381 WorkspaceStorageServices1425 ScopeStorageServices
1382> =>1426> =>
1383 Effect.gen(function* () {1427 Effect.gen(function* () {
1384 const adapter = getSourceAdapterForSource(input.source);1428 const adapter = getSourceAdapterForSource(input.source);
@@ -1404,7 +1448,7 @@ const startOauth2PkceSourceCredentialSetup = (input: { · 19 unmodified lines
1404 const state = crypto.randomUUID();1448 const state = crypto.randomUUID();
1405 const completionUrl = resolveSourceCredentialOauthCompleteUrl({1449 const completionUrl = resolveSourceCredentialOauthCompleteUrl({
1406 baseUrl: input.baseUrl,1450 baseUrl: input.baseUrl,
1407 workspaceId: input.source.workspaceId,1451 scopeId: input.source.scopeId,
1408 sourceId: input.source.id,1452 sourceId: input.source.id,
1409 });1453 });
1410 const redirectMode = input.redirectModeOverride ?? oauthClient.redirectMode;1454 const redirectMode = input.redirectModeOverride ?? oauthClient.redirectMode;
@@ -1429,9 +1473,9 @@ const startOauth2PkceSourceCredentialSetup = (input: { · 18 unmodified lines
1429 1473
1430 yield* input.executorState.sourceAuthSessions.upsert({1474 yield* input.executorState.sourceAuthSessions.upsert({
1431 id: sessionId,1475 id: sessionId,
1432 workspaceId: input.source.workspaceId,1476 scopeId: input.source.scopeId,
1433 sourceId: input.source.id,1477 sourceId: input.source.id,
1434 actorAccountId: input.actorAccountId ?? null,1478 actorScopeId: input.actorScopeId ?? null,
1435 credentialSlot: "runtime",1479 credentialSlot: "runtime",
1436 executionId: input.executionId ?? null,1480 executionId: input.executionId ?? null,
1437 interactionId: input.interactionId ?? null,1481 interactionId: input.interactionId ?? null,
@@ -1463,7 +1507,7 @@ const startOauth2PkceSourceCredentialSetup = (input: { · 25 unmodified lines
1463 });1507 });
1464 1508
1465 const authRequiredSource = yield* updateSourceStatus(input.sourceStore, input.source, {1509 const authRequiredSource = yield* updateSourceStatus(input.sourceStore, input.source, {
1466 actorAccountId: input.actorAccountId,1510 actorScopeId: input.actorScopeId,
1467 status: "auth_required",1511 status: "auth_required",
1468 lastError: null,1512 lastError: null,
1469 });1513 });
@@ -1486,13 +1530,13 @@ const startOauth2PkceSourceCredentialSetup = (input: { · 16 unmodified lines
1486const startProviderOauthBatchCredentialSetup = (input: {1530const startProviderOauthBatchCredentialSetup = (input: {
1487 executorState: ExecutorStateStoreShape;1531 executorState: ExecutorStateStoreShape;
1488 sourceStore: RuntimeSourceStore;1532 sourceStore: RuntimeSourceStore;
1489 workspaceId: WorkspaceId;1533 scopeId: ScopeId;
1490 actorAccountId?: AccountId | null;1534 actorScopeId?: ScopeId | null;
1491 executionId?: SourceAuthSession["executionId"];1535 executionId?: SourceAuthSession["executionId"];
1492 interactionId?: SourceAuthSession["interactionId"];1536 interactionId?: SourceAuthSession["interactionId"];
1493 baseUrl: string;1537 baseUrl: string;
1494 redirectModeOverride?: WorkspaceSourceOauthClientRedirectMode;1538 redirectModeOverride?: ScopedSourceOauthClientRedirectMode;
1495 workspaceOauthClient: ResolvedWorkspaceOauthClient;1539 scopeOauthClient: ResolvedScopeOauthClient;
1496 setupConfig: SourceAdapterOauth2SetupConfig;1540 setupConfig: SourceAdapterOauth2SetupConfig;
1497 targetSources: ReadonlyArray<{1541 targetSources: ReadonlyArray<{
1498 source: Source;1542 source: Source;
@@ -1502,7 +1546,7 @@ const startProviderOauthBatchCredentialSetup = (input: { · 3 unmodified lines
1502 sessionId: SourceAuthSession["id"];1546 sessionId: SourceAuthSession["id"];
1503 authorizationUrl: string;1547 authorizationUrl: string;
1504 source: Source;1548 source: Source;
1505}, Error, WorkspaceStorageServices> =>1549}, Error, ScopeStorageServices> =>
1506 Effect.gen(function* () {1550 Effect.gen(function* () {
1507 if (input.targetSources.length === 0) {1551 if (input.targetSources.length === 0) {
1508 return yield* runtimeEffectError("sources/source-auth-service", "Provider OAuth setup requires at least one target source");1552 return yield* runtimeEffectError("sources/source-auth-service", "Provider OAuth setup requires at least one target source");
@@ -1512,9 +1556,9 @@ const startProviderOauthBatchCredentialSetup = (input: { · 3 unmodified lines
1512 const state = crypto.randomUUID();1556 const state = crypto.randomUUID();
1513 const completionUrl = resolveWorkspaceProviderOauthCompleteUrl({1557 const completionUrl = resolveWorkspaceProviderOauthCompleteUrl({
1514 baseUrl: input.baseUrl,1558 baseUrl: input.baseUrl,
1515 workspaceId: input.workspaceId,1559 scopeId: input.scopeId,
1516 });1560 });
1517 const redirectMode = input.redirectModeOverride ?? input.workspaceOauthClient.redirectMode;1561 const redirectMode = input.redirectModeOverride ?? input.scopeOauthClient.redirectMode;
1518 const redirectServer = redirectMode === "loopback"1562 const redirectServer = redirectMode === "loopback"
1519 ? yield* startOauthLoopbackRedirectServer({1563 ? yield* startOauthLoopbackRedirectServer({
1520 completionUrl,1564 completionUrl,
@@ -1526,7 +1570,7 @@ const startProviderOauthBatchCredentialSetup = (input: { · 5 unmodified lines
1526 return yield* Effect.gen(function* () {1570 return yield* Effect.gen(function* () {
1527 const authorizationUrl = buildOAuth2AuthorizationUrl({1571 const authorizationUrl = buildOAuth2AuthorizationUrl({
1528 authorizationEndpoint: input.setupConfig.authorizationEndpoint,1572 authorizationEndpoint: input.setupConfig.authorizationEndpoint,
1529 clientId: input.workspaceOauthClient.clientId,1573 clientId: input.scopeOauthClient.clientId,
1530 redirectUri,1574 redirectUri,
1531 scopes: [...normalizeScopes(input.setupConfig.scopes)],1575 scopes: [...normalizeScopes(input.setupConfig.scopes)],
1532 state,1576 state,
@@ -1537,9 +1581,9 @@ const startProviderOauthBatchCredentialSetup = (input: { · 4 unmodified lines
1537 1581
1538 yield* input.executorState.sourceAuthSessions.upsert({1582 yield* input.executorState.sourceAuthSessions.upsert({
1539 id: sessionId,1583 id: sessionId,
1540 workspaceId: input.workspaceId,1584 scopeId: input.scopeId,
1541 sourceId: SourceIdSchema.make(`oauth_provider_${crypto.randomUUID()}`),1585 sourceId: SourceIdSchema.make(`oauth_provider_${crypto.randomUUID()}`),
1542 actorAccountId: input.actorAccountId ?? null,1586 actorScopeId: input.actorScopeId ?? null,
1543 credentialSlot: "runtime",1587 credentialSlot: "runtime",
1544 executionId: input.executionId ?? null,1588 executionId: input.executionId ?? null,
1545 interactionId: input.interactionId ?? null,1589 interactionId: input.interactionId ?? null,
@@ -1552,7 +1596,7 @@ const startProviderOauthBatchCredentialSetup = (input: { · 6 unmodified lines
1552 authorizationEndpoint: input.setupConfig.authorizationEndpoint,1596 authorizationEndpoint: input.setupConfig.authorizationEndpoint,
1553 tokenEndpoint: input.setupConfig.tokenEndpoint,1597 tokenEndpoint: input.setupConfig.tokenEndpoint,
1554 redirectUri,1598 redirectUri,
1555 oauthClientId: input.workspaceOauthClient.id,1599 oauthClientId: input.scopeOauthClient.id,
1556 clientAuthentication: input.setupConfig.clientAuthentication,1600 clientAuthentication: input.setupConfig.clientAuthentication,
1557 scopes: [...normalizeScopes(input.setupConfig.scopes)],1601 scopes: [...normalizeScopes(input.setupConfig.scopes)],
1558 headerName: input.setupConfig.headerName,1602 headerName: input.setupConfig.headerName,
@@ -1577,7 +1621,7 @@ const startProviderOauthBatchCredentialSetup = (input: { · 18 unmodified lines
1577 input.sourceStore,1621 input.sourceStore,
1578 input.targetSources[0]!.source,1622 input.targetSources[0]!.source,
1579 {1623 {
1580 actorAccountId: input.actorAccountId,1624 actorScopeId: input.actorScopeId,
1581 status: "auth_required",1625 status: "auth_required",
1582 lastError: null,1626 lastError: null,
1583 },1627 },
@@ -1587,7 +1631,7 @@ const startProviderOauthBatchCredentialSetup = (input: { · 3 unmodified lines
1587 input.targetSources.slice(1),1631 input.targetSources.slice(1),
1588 (target) =>1632 (target) =>
1589 updateSourceStatus(input.sourceStore, target.source, {1633 updateSourceStatus(input.sourceStore, target.source, {
1590 actorAccountId: input.actorAccountId,1634 actorScopeId: input.actorScopeId,
1591 status: "auth_required",1635 status: "auth_required",
1592 lastError: null,1636 lastError: null,
1593 }).pipe(Effect.asVoid),1637 }).pipe(Effect.asVoid),
@@ -1612,31 +1656,33 @@ const connectSourcesWithProviderRuntimeAuth = (input: { · 18 unmodified lines
1612 executorState: ExecutorStateStoreShape;1656 executorState: ExecutorStateStoreShape;
1613 sourceStore: RuntimeSourceStore;1657 sourceStore: RuntimeSourceStore;
1614 sourceCatalogSync: RuntimeSourceCatalogSyncShape;1658 sourceCatalogSync: RuntimeSourceCatalogSyncShape;
1615 workspaceId: WorkspaceId;1659 scopeId: ScopeId;
1616 actorAccountId?: AccountId | null;1660 actorScopeId?: ScopeId | null;
1617 executionId?: SourceAuthSession["executionId"];1661 executionId?: SourceAuthSession["executionId"];
1618 interactionId?: SourceAuthSession["interactionId"];1662 interactionId?: SourceAuthSession["interactionId"];
1619 baseUrl?: string | null;1663 baseUrl?: string | null;
1620 getLocalServerBaseUrl?: () => string | undefined;1664 getLocalServerBaseUrl?: () => string | undefined;
1621 workspaceOauthClient: ResolvedWorkspaceOauthClient;1665 scopeOauthClient: ResolvedScopeOauthClient;
1622 targets: ReadonlyArray<RuntimeProviderAuthTarget>;1666 targets: ReadonlyArray<RuntimeProviderAuthTarget>;
1623}): Effect.Effect<RuntimeProviderAuthResult, Error, WorkspaceStorageServices> =>1667 localScopeState?: RuntimeLocalScopeState;
1668}): Effect.Effect<RuntimeProviderAuthResult, Error, ScopeStorageServices> =>
1624 Effect.gen(function* () {1669 Effect.gen(function* () {
1625 const setupConfig = yield* mergeRuntimeProviderAuthSetupConfig(input.targets);1670 const setupConfig = yield* mergeRuntimeProviderAuthSetupConfig(input.targets);
1626 const reusableGrant = yield* findReusableProviderGrant({1671 const reusableGrant = yield* findReusableProviderGrant({
1627 executorState: input.executorState,1672 executorState: input.executorState,
1628 workspaceId: input.workspaceId,1673 scopeId: input.scopeId,
1629 actorAccountId: input.actorAccountId,1674 actorScopeId: input.actorScopeId,
1630 providerKey: setupConfig.providerKey,1675 providerKey: setupConfig.providerKey,
1631 oauthClientId: input.workspaceOauthClient.id,1676 oauthClientId: input.scopeOauthClient.id,
1632 requiredScopes: setupConfig.scopes,1677 requiredScopes: setupConfig.scopes,
1678 localScopeState: input.localScopeState,
1633 });1679 });
1634 1680
1635 if (Option.isSome(reusableGrant)) {1681 if (Option.isSome(reusableGrant)) {
1636 const connectedSources = yield* attachProviderGrantToSources({1682 const connectedSources = yield* attachProviderGrantToSources({
1637 sourceStore: input.sourceStore,1683 sourceStore: input.sourceStore,
1638 sourceCatalogSync: input.sourceCatalogSync,1684 sourceCatalogSync: input.sourceCatalogSync,
1639 actorAccountId: input.actorAccountId,1685 actorScopeId: input.actorScopeId,
1640 grantId: reusableGrant.value.id,1686 grantId: reusableGrant.value.id,
1641 providerKey: reusableGrant.value.providerKey,1687 providerKey: reusableGrant.value.providerKey,
1642 headerName: reusableGrant.value.headerName,1688 headerName: reusableGrant.value.headerName,
@@ -1665,13 +1711,13 @@ const connectSourcesWithProviderRuntimeAuth = (input: { · 22 unmodified lines
1665 const oauthRequired = yield* startProviderOauthBatchCredentialSetup({1711 const oauthRequired = yield* startProviderOauthBatchCredentialSetup({
1666 executorState: input.executorState,1712 executorState: input.executorState,
1667 sourceStore: input.sourceStore,1713 sourceStore: input.sourceStore,
1668 workspaceId: input.workspaceId,1714 scopeId: input.scopeId,
1669 actorAccountId: input.actorAccountId,1715 actorScopeId: input.actorScopeId,
1670 executionId: input.executionId,1716 executionId: input.executionId,
1671 interactionId: input.interactionId,1717 interactionId: input.interactionId,
1672 baseUrl,1718 baseUrl,
1673 redirectModeOverride: requestBaseUrl ? "app_callback" : undefined,1719 redirectModeOverride: requestBaseUrl ? "app_callback" : undefined,
1674 workspaceOauthClient: input.workspaceOauthClient,1720 scopeOauthClient: input.scopeOauthClient,
1675 setupConfig,1721 setupConfig,
1676 targetSources: input.targets.map((target) => ({1722 targetSources: input.targets.map((target) => ({
1677 source: target.source,1723 source: target.source,
@@ -1683,9 +1729,9 @@ const connectSourcesWithProviderRuntimeAuth = (input: { · 5 unmodified lines
1683 input.targets,1729 input.targets,
1684 (target) =>1730 (target) =>
1685 input.sourceStore.loadSourceById({1731 input.sourceStore.loadSourceById({
1686 workspaceId: target.source.workspaceId,1732 scopeId: target.source.scopeId,
1687 sourceId: target.source.id,1733 sourceId: target.source.id,
1688 actorAccountId: input.actorAccountId,1734 actorScopeId: input.actorScopeId,
1689 }),1735 }),
1690 { discard: false },1736 { discard: false },
1691 );1737 );
@@ -1701,7 +1747,7 @@ const connectSourcesWithProviderRuntimeAuth = (input: { · 9 unmodified lines
1701const attachProviderGrantToSources = (input: {1747const attachProviderGrantToSources = (input: {
1702 sourceStore: RuntimeSourceStore;1748 sourceStore: RuntimeSourceStore;
1703 sourceCatalogSync: RuntimeSourceCatalogSyncShape;1749 sourceCatalogSync: RuntimeSourceCatalogSyncShape;
1704 actorAccountId?: AccountId | null;1750 actorScopeId?: ScopeId | null;
1705 grantId: ProviderAuthGrant["id"];1751 grantId: ProviderAuthGrant["id"];
1706 providerKey: string;1752 providerKey: string;
1707 headerName: string;1753 headerName: string;
@@ -1710,13 +1756,13 @@ const attachProviderGrantToSources = (input: { · 2 unmodified lines
1710 source: Source;1756 source: Source;
1711 requiredScopes: ReadonlyArray<string>;1757 requiredScopes: ReadonlyArray<string>;
1712 }>;1758 }>;
1713}): Effect.Effect<ReadonlyArray<Source>, Error, WorkspaceStorageServices> =>1759}): Effect.Effect<ReadonlyArray<Source>, Error, ScopeStorageServices> =>
1714 Effect.forEach(1760 Effect.forEach(
1715 input.targets,1761 input.targets,
1716 (target) =>1762 (target) =>
1717 Effect.gen(function* () {1763 Effect.gen(function* () {
1718 const connectedSource = yield* updateSourceStatus(input.sourceStore, target.source, {1764 const connectedSource = yield* updateSourceStatus(input.sourceStore, target.source, {
1719 actorAccountId: input.actorAccountId,1765 actorScopeId: input.actorScopeId,
1720 status: "connected",1766 status: "connected",
1721 lastError: null,1767 lastError: null,
1722 auth: {1768 auth: {
@@ -1731,7 +1777,7 @@ const attachProviderGrantToSources = (input: { · 8 unmodified lines
1731 1777
1732 yield* input.sourceCatalogSync.sync({1778 yield* input.sourceCatalogSync.sync({
1733 source: connectedSource,1779 source: connectedSource,
1734 actorAccountId: input.actorAccountId,1780 actorScopeId: input.actorScopeId,
1735 });1781 });
1736 1782
1737 return connectedSource;1783 return connectedSource;
@@ -1742,18 +1788,18 @@ const attachProviderGrantToSources = (input: { · 4 unmodified lines
1742const removeProviderAuthGrantInternal = (input: {1788const removeProviderAuthGrantInternal = (input: {
1743 executorState: ExecutorStateStoreShape;1789 executorState: ExecutorStateStoreShape;
1744 sourceStore: RuntimeSourceStore;1790 sourceStore: RuntimeSourceStore;
1745 workspaceId: WorkspaceId;1791 scopeId: ScopeId;
1746 grantId: ProviderAuthGrant["id"];1792 grantId: ProviderAuthGrant["id"];
1747 deleteSecretMaterial: DeleteSecretMaterial;1793 deleteSecretMaterial: DeleteSecretMaterial;
1748}): Effect.Effect<boolean, Error, WorkspaceStorageServices> =>1794}): Effect.Effect<boolean, Error, ScopeStorageServices> =>
1749 Effect.gen(function* () {1795 Effect.gen(function* () {
1750 const grantOption = yield* input.executorState.providerAuthGrants.getById(input.grantId);1796 const grantOption = yield* input.executorState.providerAuthGrants.getById(input.grantId);
1751 if (Option.isNone(grantOption) || grantOption.value.workspaceId !== input.workspaceId) {1797 if (Option.isNone(grantOption) || grantOption.value.scopeId !== input.scopeId) {
1752 return false;1798 return false;
1753 }1799 }
1754 1800
1755 const references = yield* listProviderGrantRefArtifacts(input.executorState, {1801 const references = yield* listProviderGrantRefArtifacts(input.executorState, {
1756 workspaceId: input.workspaceId,1802 scopeId: input.scopeId,
1757 grantId: input.grantId,1803 grantId: input.grantId,
1758 });1804 });
1759 1805
@@ -1762,9 +1808,9 @@ const removeProviderAuthGrantInternal = (input: { · 2 unmodified lines
1762 (artifact) =>1808 (artifact) =>
1763 Effect.gen(function* () {1809 Effect.gen(function* () {
1764 const latestSource = yield* input.sourceStore.loadSourceById({1810 const latestSource = yield* input.sourceStore.loadSourceById({
1765 workspaceId: artifact.workspaceId,1811 scopeId: artifact.scopeId,
1766 sourceId: artifact.sourceId,1812 sourceId: artifact.sourceId,
1767 actorAccountId: artifact.actorAccountId,1813 actorScopeId: artifact.actorScopeId,
1768 }).pipe(1814 }).pipe(
1769 Effect.catchAll(() => Effect.succeed(null)),1815 Effect.catchAll(() => Effect.succeed(null)),
1770 );1816 );
@@ -1773,10 +1819,10 @@ const removeProviderAuthGrantInternal = (input: { · 2 unmodified lines
1773 yield* removeAuthLeaseAndSecrets(input.executorState, {1819 yield* removeAuthLeaseAndSecrets(input.executorState, {
1774 authArtifactId: artifact.id,1820 authArtifactId: artifact.id,
1775 }, input.deleteSecretMaterial);1821 }, input.deleteSecretMaterial);
1776 yield* input.executorState.authArtifacts.removeByWorkspaceSourceAndActor({1822 yield* input.executorState.authArtifacts.removeByScopeSourceAndActor({
1777 workspaceId: artifact.workspaceId,1823 scopeId: artifact.scopeId,
1778 sourceId: artifact.sourceId,1824 sourceId: artifact.sourceId,
1779 actorAccountId: artifact.actorAccountId,1825 actorScopeId: artifact.actorScopeId,
1780 slot: artifact.slot,1826 slot: artifact.slot,
1781 });1827 });
1782 return;1828 return;
@@ -1790,7 +1836,7 @@ const removeProviderAuthGrantInternal = (input: { · 7 unmodified lines
1790 importAuth: artifact.slot === "import" ? { kind: "none" } : latestSource.importAuth,1836 importAuth: artifact.slot === "import" ? { kind: "none" } : latestSource.importAuth,
1791 updatedAt: Date.now(),1837 updatedAt: Date.now(),
1792 }, {1838 }, {
1793 actorAccountId: artifact.actorAccountId,1839 actorScopeId: artifact.actorScopeId,
1794 }).pipe(Effect.asVoid);1840 }).pipe(Effect.asVoid);
1795 }),1841 }),
1796 { discard: true },1842 { discard: true },
@@ -1809,8 +1855,8 @@ const connectMcpSourceInternal = (input: { · 12 unmodified lines
1809 sourceCatalogSync: RuntimeSourceCatalogSyncShape;1855 sourceCatalogSync: RuntimeSourceCatalogSyncShape;
1810 getLocalServerBaseUrl?: () => string | undefined;1856 getLocalServerBaseUrl?: () => string | undefined;
1811 baseUrl?: string | null;1857 baseUrl?: string | null;
1812 workspaceId: WorkspaceId;1858 scopeId: ScopeId;
1813 actorAccountId?: AccountId | null;1859 actorScopeId?: ScopeId | null;
1814 sourceId?: Source["id"] | null;1860 sourceId?: Source["id"] | null;
1815 executionId?: SourceAuthSession["executionId"];1861 executionId?: SourceAuthSession["executionId"];
1816 interactionId?: SourceAuthSession["interactionId"];1862 interactionId?: SourceAuthSession["interactionId"];
@@ -1827,7 +1873,7 @@ const connectMcpSourceInternal = (input: { · 10 unmodified lines
1827 cwd?: string | null;1873 cwd?: string | null;
1828 mcpDiscoveryElicitation?: McpDiscoveryElicitationContext;1874 mcpDiscoveryElicitation?: McpDiscoveryElicitationContext;
1829 resolveSecretMaterial: ResolveSecretMaterial;1875 resolveSecretMaterial: ResolveSecretMaterial;
1830}): Effect.Effect<McpSourceConnectResult, Error, WorkspaceStorageServices> =>1876}): Effect.Effect<McpSourceConnectResult, Error, ScopeStorageServices> =>
1831 Effect.gen(function* () {1877 Effect.gen(function* () {
1832 const lookupEndpoint = input.sourceId1878 const lookupEndpoint = input.sourceId
1833 ? null1879 ? null
@@ -1840,9 +1886,9 @@ const connectMcpSourceInternal = (input: { · 6 unmodified lines
1840 const existing = yield* (1886 const existing = yield* (
1841 input.sourceId1887 input.sourceId
1842 ? input.sourceStore.loadSourceById({1888 ? input.sourceStore.loadSourceById({
1843 workspaceId: input.workspaceId,1889 scopeId: input.scopeId,
1844 sourceId: input.sourceId,1890 sourceId: input.sourceId,
1845 actorAccountId: input.actorAccountId,1891 actorScopeId: input.actorScopeId,
1846 }).pipe(1892 }).pipe(
1847 Effect.flatMap((source) =>1893 Effect.flatMap((source) =>
1848 sourceAdapterRequiresInteractiveConnect(source.kind)1894 sourceAdapterRequiresInteractiveConnect(source.kind)
@@ -1850,8 +1896,8 @@ const connectMcpSourceInternal = (input: { · 1 unmodified line
1850 : Effect.fail(runtimeEffectError("sources/source-auth-service", `Expected MCP source, received ${source.kind}`)),1896 : Effect.fail(runtimeEffectError("sources/source-auth-service", `Expected MCP source, received ${source.kind}`)),
1851 ),1897 ),
1852 )1898 )
1853 : input.sourceStore.loadSourcesInWorkspace(input.workspaceId, {1899 : input.sourceStore.loadSourcesInScope(input.scopeId, {
1854 actorAccountId: input.actorAccountId,1900 actorScopeId: input.actorScopeId,
1855 }).pipe(1901 }).pipe(
1856 Effect.map((sources) =>1902 Effect.map((sources) =>
1857 sources.find(1903 sources.find(
@@ -1943,7 +1989,7 @@ const connectMcpSourceInternal = (input: { · 85 unmodified lines
1943 now,1989 now,
1944 })1990 })
1945 : yield* createSourceFromPayload({1991 : yield* createSourceFromPayload({
1946 workspaceId: input.workspaceId,1992 scopeId: input.scopeId,
1947 sourceId: SourceIdSchema.make(`src_${crypto.randomUUID()}`),1993 sourceId: SourceIdSchema.make(`src_${crypto.randomUUID()}`),
1948 payload: {1994 payload: {
1949 name: chosenName,1995 name: chosenName,
@@ -1969,11 +2015,11 @@ const connectMcpSourceInternal = (input: { · 19 unmodified lines
1969 });2015 });
1970 2016
1971 const persistedDraft = yield* input.sourceStore.persistSource(draftSource, {2017 const persistedDraft = yield* input.sourceStore.persistSource(draftSource, {
1972 actorAccountId: input.actorAccountId,2018 actorScopeId: input.actorScopeId,
1973 });2019 });
1974 yield* input.sourceCatalogSync.sync({2020 yield* input.sourceCatalogSync.sync({
1975 source: persistedDraft,2021 source: persistedDraft,
1976 actorAccountId: input.actorAccountId,2022 actorScopeId: input.actorScopeId,
1977 });2023 });
1978 2024
1979 const discovered = yield* Effect.either(2025 const discovered = yield* Effect.either(
@@ -1988,7 +2034,7 @@ const connectMcpSourceInternal = (input: { · 8 unmodified lines
1988 onRight: (result) =>2034 onRight: (result) =>
1989 Effect.gen(function* () {2035 Effect.gen(function* () {
1990 const connected = yield* updateSourceStatus(input.sourceStore, persistedDraft, {2036 const connected = yield* updateSourceStatus(input.sourceStore, persistedDraft, {
1991 actorAccountId: input.actorAccountId,2037 actorScopeId: input.actorScopeId,
1992 status: "connected",2038 status: "connected",
1993 lastError: null,2039 lastError: null,
1994 auth: { kind: "none" },2040 auth: { kind: "none" },
@@ -2003,7 +2049,7 @@ const connectMcpSourceInternal = (input: { · 8 unmodified lines
2003 return yield* Either.match(indexed, {2049 return yield* Either.match(indexed, {
2004 onLeft: (error) =>2050 onLeft: (error) =>
2005 updateSourceStatus(input.sourceStore, connected, {2051 updateSourceStatus(input.sourceStore, connected, {
2006 actorAccountId: input.actorAccountId,2052 actorScopeId: input.actorScopeId,
2007 status: "error",2053 status: "error",
2008 lastError: error.message,2054 lastError: error.message,
2009 }).pipe(2055 }).pipe(
@@ -2031,7 +2077,7 @@ const connectMcpSourceInternal = (input: { · 21 unmodified lines
2031 const state = crypto.randomUUID();2077 const state = crypto.randomUUID();
2032 const redirectUrl = resolveSourceCredentialOauthCompleteUrl({2078 const redirectUrl = resolveSourceCredentialOauthCompleteUrl({
2033 baseUrl: localServerBaseUrl,2079 baseUrl: localServerBaseUrl,
2034 workspaceId: input.workspaceId,2080 scopeId: input.scopeId,
2035 sourceId: persistedDraft.id,2081 sourceId: persistedDraft.id,
2036 });2082 });
2037 const oauthStart = yield* startMcpOAuthAuthorization({2083 const oauthStart = yield* startMcpOAuthAuthorization({
@@ -2041,7 +2087,7 @@ const connectMcpSourceInternal = (input: { · 3 unmodified lines
2041 });2087 });
2042 2088
2043 const authRequiredSource = yield* updateSourceStatus(input.sourceStore, persistedDraft, {2089 const authRequiredSource = yield* updateSourceStatus(input.sourceStore, persistedDraft, {
2044 actorAccountId: input.actorAccountId,2090 actorScopeId: input.actorScopeId,
2045 status: "auth_required",2091 status: "auth_required",
2046 lastError: null,2092 lastError: null,
2047 });2093 });
@@ -2049,9 +2095,9 @@ const connectMcpSourceInternal = (input: { · 1 unmodified line
2049 const sessionNow = Date.now();2095 const sessionNow = Date.now();
2050 yield* input.executorState.sourceAuthSessions.upsert({2096 yield* input.executorState.sourceAuthSessions.upsert({
2051 id: sessionId,2097 id: sessionId,
2052 workspaceId: input.workspaceId,2098 scopeId: input.scopeId,
2053 sourceId: authRequiredSource.id,2099 sourceId: authRequiredSource.id,
2054 actorAccountId: input.actorAccountId ?? null,2100 actorScopeId: input.actorScopeId ?? null,
2055 credentialSlot: "runtime",2101 credentialSlot: "runtime",
2056 executionId: input.executionId ?? null,2102 executionId: input.executionId ?? null,
2057 interactionId: input.interactionId ?? null,2103 interactionId: input.interactionId ?? null,
@@ -2095,16 +2141,17 @@ const addExecutorHttpSource = (input: { · 37 unmodified lines
2095 resolveSecretMaterial: ResolveSecretMaterial;2141 resolveSecretMaterial: ResolveSecretMaterial;
2096 getLocalServerBaseUrl?: () => string | undefined;2142 getLocalServerBaseUrl?: () => string | undefined;
2097 baseUrl?: string | null;2143 baseUrl?: string | null;
2098}): Effect.Effect<ExecutorSourceAddResult, Error, WorkspaceStorageServices> =>2144 localScopeState?: RuntimeLocalScopeState;
2145}): Effect.Effect<ExecutorSourceAddResult, Error, ScopeStorageServices> =>
2099 Effect.gen(function* () {2146 Effect.gen(function* () {
2100 const normalizedEndpoint = normalizeEndpoint(input.sourceInput.endpoint);2147 const normalizedEndpoint = normalizeEndpoint(input.sourceInput.endpoint);
2101 const normalizedSpecUrl = input.sourceInput.kind === "openapi"2148 const normalizedSpecUrl = input.sourceInput.kind === "openapi"
2102 ? normalizeEndpoint(input.sourceInput.specUrl)2149 ? normalizeEndpoint(input.sourceInput.specUrl)
2103 : null;2150 : null;
2104 const existingSources = yield* input.sourceStore.loadSourcesInWorkspace(2151 const existingSources = yield* input.sourceStore.loadSourcesInScope(
2105 input.sourceInput.workspaceId,2152 input.sourceInput.scopeId,
2106 {2153 {
2107 actorAccountId: input.sourceInput.actorAccountId,2154 actorScopeId: input.sourceInput.actorScopeId,
2108 },2155 },
2109 );2156 );
2110 const existing = existingSources.find((source) => {2157 const existing = existingSources.find((source) => {
@@ -2175,7 +2222,7 @@ const addExecutorHttpSource = (input: { · 64 unmodified lines
2175 now,2222 now,
2176 })2223 })
2177 : yield* createSourceFromPayload({2224 : yield* createSourceFromPayload({
2178 workspaceId: input.sourceInput.workspaceId,2225 scopeId: input.sourceInput.scopeId,
2179 sourceId: SourceIdSchema.make(`src_${crypto.randomUUID()}`),2226 sourceId: SourceIdSchema.make(`src_${crypto.randomUUID()}`),
2180 payload: {2227 payload: {
2181 name: chosenName,2228 name: chosenName,
@@ -2200,7 +2247,7 @@ const addExecutorHttpSource = (input: { · 18 unmodified lines
2200 });2247 });
2201 2248
2202 const persistedDraft = yield* input.sourceStore.persistSource(draftSource, {2249 const persistedDraft = yield* input.sourceStore.persistSource(draftSource, {
2203 actorAccountId: input.sourceInput.actorAccountId,2250 actorScopeId: input.sourceInput.actorScopeId,
2204 });2251 });
2205 2252
2206 if (shouldPromptForExecutorHttpRuntimeCredentialSetup({2253 if (shouldPromptForExecutorHttpRuntimeCredentialSetup({
@@ -2215,7 +2262,7 @@ const addExecutorHttpSource = (input: { · 8 unmodified lines
2215 executorState: input.executorState,2262 executorState: input.executorState,
2216 sourceStore: input.sourceStore,2263 sourceStore: input.sourceStore,
2217 source: persistedDraft,2264 source: persistedDraft,
2218 actorAccountId: input.sourceInput.actorAccountId,2265 actorScopeId: input.sourceInput.actorScopeId,
2219 executionId: input.sourceInput.executionId,2266 executionId: input.sourceInput.executionId,
2220 interactionId: input.sourceInput.interactionId,2267 interactionId: input.sourceInput.interactionId,
2221 baseUrl,2268 baseUrl,
@@ -2228,7 +2275,7 @@ const addExecutorHttpSource = (input: { · 6 unmodified lines
2228 }2275 }
2229 2276
2230 const authRequiredSource = yield* updateSourceStatus(input.sourceStore, persistedDraft, {2277 const authRequiredSource = yield* updateSourceStatus(input.sourceStore, persistedDraft, {
2231 actorAccountId: input.sourceInput.actorAccountId,2278 actorScopeId: input.sourceInput.actorScopeId,
2232 status: "auth_required",2279 status: "auth_required",
2233 lastError: null,2280 lastError: null,
2234 });2281 });
@@ -2246,7 +2293,7 @@ const addExecutorHttpSource = (input: { · 11 unmodified lines
2246 ...persistedDraft,2293 ...persistedDraft,
2247 status: "connected",2294 status: "connected",
2248 },2295 },
2249 actorAccountId: input.sourceInput.actorAccountId,2296 actorScopeId: input.sourceInput.actorScopeId,
2250 }),2297 }),
2251 );2298 );
2252 2299
@@ -2254,7 +2301,7 @@ const addExecutorHttpSource = (input: { · 1 unmodified line
2254 onLeft: (error) =>2301 onLeft: (error) =>
2255 isSourceCredentialRequiredError(error)2302 isSourceCredentialRequiredError(error)
2256 ? updateSourceStatus(input.sourceStore, persistedDraft, {2303 ? updateSourceStatus(input.sourceStore, persistedDraft, {
2257 actorAccountId: input.sourceInput.actorAccountId,2304 actorScopeId: input.sourceInput.actorScopeId,
2258 status: "auth_required",2305 status: "auth_required",
2259 lastError: null,2306 lastError: null,
2260 }).pipe(2307 }).pipe(
@@ -2267,7 +2314,7 @@ const addExecutorHttpSource = (input: { · 6 unmodified lines
2267 ),2314 ),
2268 )2315 )
2269 : updateSourceStatus(input.sourceStore, persistedDraft, {2316 : updateSourceStatus(input.sourceStore, persistedDraft, {
2270 actorAccountId: input.sourceInput.actorAccountId,2317 actorScopeId: input.sourceInput.actorScopeId,
2271 status: "error",2318 status: "error",
2272 lastError: error.message,2319 lastError: error.message,
2273 }).pipe(2320 }).pipe(
@@ -2275,7 +2322,7 @@ const addExecutorHttpSource = (input: { · 1 unmodified line
2275 ),2322 ),
2276 onRight: () =>2323 onRight: () =>
2277 updateSourceStatus(input.sourceStore, persistedDraft, {2324 updateSourceStatus(input.sourceStore, persistedDraft, {
2278 actorAccountId: input.sourceInput.actorAccountId,2325 actorScopeId: input.sourceInput.actorScopeId,
2279 status: "connected",2326 status: "connected",
2280 lastError: null,2327 lastError: null,
2281 }).pipe(2328 }).pipe(
@@ -2308,7 +2355,8 @@ const addExecutorGoogleDiscoverySource = (input: { · 26 unmodified lines
2308 resolveSecretMaterial: ResolveSecretMaterial;2355 resolveSecretMaterial: ResolveSecretMaterial;
2309 getLocalServerBaseUrl?: () => string | undefined;2356 getLocalServerBaseUrl?: () => string | undefined;
2310 baseUrl?: string | null;2357 baseUrl?: string | null;
2311}): Effect.Effect<ExecutorSourceAddResult, Error, WorkspaceStorageServices> =>2358 localScopeState?: RuntimeLocalScopeState;
2359}): Effect.Effect<ExecutorSourceAddResult, Error, ScopeStorageServices> =>
2312 Effect.gen(function* () {2360 Effect.gen(function* () {
2313 const normalizedService = input.sourceInput.service.trim();2361 const normalizedService = input.sourceInput.service.trim();
2314 const normalizedVersion = input.sourceInput.version.trim();2362 const normalizedVersion = input.sourceInput.version.trim();
@@ -2316,10 +2364,10 @@ const addExecutorGoogleDiscoverySource = (input: { · 1 unmodified line
2316 trimOrNull(input.sourceInput.discoveryUrl)2364 trimOrNull(input.sourceInput.discoveryUrl)
2317 ?? defaultGoogleDiscoveryUrl(normalizedService, normalizedVersion),2365 ?? defaultGoogleDiscoveryUrl(normalizedService, normalizedVersion),
2318 );2366 );
2319 const existingSources = yield* input.sourceStore.loadSourcesInWorkspace(2367 const existingSources = yield* input.sourceStore.loadSourcesInScope(
2320 input.sourceInput.workspaceId,2368 input.sourceInput.scopeId,
2321 {2369 {
2322 actorAccountId: input.sourceInput.actorAccountId,2370 actorScopeId: input.sourceInput.actorScopeId,
2323 },2371 },
2324 );2372 );
2325 const existing = existingSources.find((source) => {2373 const existing = existingSources.find((source) => {
@@ -2397,7 +2445,7 @@ const addExecutorGoogleDiscoverySource = (input: { · 71 unmodified lines
2397 now,2445 now,
2398 })2446 })
2399 : yield* createSourceFromPayload({2447 : yield* createSourceFromPayload({
2400 workspaceId: input.sourceInput.workspaceId,2448 scopeId: input.sourceInput.scopeId,
2401 sourceId: SourceIdSchema.make(`src_${crypto.randomUUID()}`),2449 sourceId: SourceIdSchema.make(`src_${crypto.randomUUID()}`),
2402 payload: {2450 payload: {
2403 name: chosenName,2451 name: chosenName,
@@ -2421,7 +2469,7 @@ const addExecutorGoogleDiscoverySource = (input: { · 17 unmodified lines
2421 });2469 });
2422 2470
2423 const persistedDraft = yield* input.sourceStore.persistSource(draftSource, {2471 const persistedDraft = yield* input.sourceStore.persistSource(draftSource, {
2424 actorAccountId: input.sourceInput.actorAccountId,2472 actorScopeId: input.sourceInput.actorScopeId,
2425 });2473 });
2426 const googleAdapter = getSourceAdapterForSource(persistedDraft);2474 const googleAdapter = getSourceAdapterForSource(persistedDraft);
2427 const providerAuthTarget = yield* resolveRuntimeProviderAuthTarget(persistedDraft);2475 const providerAuthTarget = yield* resolveRuntimeProviderAuthTarget(persistedDraft);
@@ -2431,18 +2479,19 @@ const addExecutorGoogleDiscoverySource = (input: { · 3 unmodified lines
2431 && input.sourceInput.auth === undefined2479 && input.sourceInput.auth === undefined
2432 && persistedDraft.auth.kind === "none"2480 && persistedDraft.auth.kind === "none"
2433 ) {2481 ) {
2434 let workspaceOauthClient: ResolvedWorkspaceOauthClient | null = null;2482 let scopeOauthClient: ResolvedScopeOauthClient | null = null;
2435 if (input.sourceInput.workspaceOauthClientId) {2483 if (input.sourceInput.scopeOauthClientId) {
2436 workspaceOauthClient = yield* resolveWorkspaceOauthClientById({2484 scopeOauthClient = yield* resolveScopeOauthClientById({
2437 executorState: input.executorState,2485 executorState: input.executorState,
2438 workspaceId: persistedDraft.workspaceId,2486 scopeId: persistedDraft.scopeId,
2439 oauthClientId: input.sourceInput.workspaceOauthClientId,2487 oauthClientId: input.sourceInput.scopeOauthClientId,
2440 providerKey: providerAuthTarget.setupConfig.providerKey,2488 providerKey: providerAuthTarget.setupConfig.providerKey,
2489 localScopeState: input.localScopeState,
2441 });2490 });
2442 } else if (input.sourceInput.oauthClient) {2491 } else if (input.sourceInput.oauthClient) {
2443 workspaceOauthClient = yield* createWorkspaceOauthClient({2492 scopeOauthClient = yield* createScopeOauthClient({
2444 executorState: input.executorState,2493 executorState: input.executorState,
2445 workspaceId: persistedDraft.workspaceId,2494 scopeId: persistedDraft.scopeId,
2446 providerKey: providerAuthTarget.setupConfig.providerKey,2495 providerKey: providerAuthTarget.setupConfig.providerKey,
2447 oauthClient: input.sourceInput.oauthClient,2496 oauthClient: input.sourceInput.oauthClient,
2448 label: `${chosenName} OAuth Client`,2497 label: `${chosenName} OAuth Client`,
@@ -2451,7 +2500,7 @@ const addExecutorGoogleDiscoverySource = (input: { · 2 unmodified lines
2451 });2500 });
2452 }2501 }
2453 2502
2454 if (workspaceOauthClient === null) {2503 if (scopeOauthClient === null) {
2455 return yield* runtimeEffectError(2504 return yield* runtimeEffectError(
2456 "sources/source-auth-service",2505 "sources/source-auth-service",
2457 `${providerAuthTarget.setupConfig.providerKey} shared auth requires a workspace OAuth client`,2506 `${providerAuthTarget.setupConfig.providerKey} shared auth requires a workspace OAuth client`,
@@ -2462,14 +2511,15 @@ const addExecutorGoogleDiscoverySource = (input: { · 4 unmodified lines
2462 executorState: input.executorState,2511 executorState: input.executorState,
2463 sourceStore: input.sourceStore,2512 sourceStore: input.sourceStore,
2464 sourceCatalogSync: input.sourceCatalogSync,2513 sourceCatalogSync: input.sourceCatalogSync,
2465 workspaceId: persistedDraft.workspaceId,2514 scopeId: persistedDraft.scopeId,
2466 actorAccountId: input.sourceInput.actorAccountId,2515 actorScopeId: input.sourceInput.actorScopeId,
2467 executionId: input.sourceInput.executionId,2516 executionId: input.sourceInput.executionId,
2468 interactionId: input.sourceInput.interactionId,2517 interactionId: input.sourceInput.interactionId,
2469 baseUrl: input.baseUrl,2518 baseUrl: input.baseUrl,
2470 getLocalServerBaseUrl: input.getLocalServerBaseUrl,2519 getLocalServerBaseUrl: input.getLocalServerBaseUrl,
2471 workspaceOauthClient,2520 scopeOauthClient,
2472 targets: [providerAuthTarget],2521 targets: [providerAuthTarget],
2522 localScopeState: input.localScopeState,
2473 });2523 });
2474 2524
2475 return providerAuthResult.kind === "connected"2525 return providerAuthResult.kind === "connected"
@@ -2507,7 +2557,7 @@ const addExecutorGoogleDiscoverySource = (input: { · 31 unmodified lines
2507 executorState: input.executorState,2557 executorState: input.executorState,
2508 sourceStore: input.sourceStore,2558 sourceStore: input.sourceStore,
2509 source: persistedDraft,2559 source: persistedDraft,
2510 actorAccountId: input.sourceInput.actorAccountId,2560 actorScopeId: input.sourceInput.actorScopeId,
2511 executionId: input.sourceInput.executionId,2561 executionId: input.sourceInput.executionId,
2512 interactionId: input.sourceInput.interactionId,2562 interactionId: input.sourceInput.interactionId,
2513 baseUrl,2563 baseUrl,
@@ -2520,7 +2570,7 @@ const addExecutorGoogleDiscoverySource = (input: { · 6 unmodified lines
2520 }2570 }
2521 2571
2522 const authRequiredSource = yield* updateSourceStatus(input.sourceStore, persistedDraft, {2572 const authRequiredSource = yield* updateSourceStatus(input.sourceStore, persistedDraft, {
2523 actorAccountId: input.sourceInput.actorAccountId,2573 actorScopeId: input.sourceInput.actorScopeId,
2524 status: "auth_required",2574 status: "auth_required",
2525 lastError: null,2575 lastError: null,
2526 });2576 });
@@ -2538,7 +2588,7 @@ const addExecutorGoogleDiscoverySource = (input: { · 11 unmodified lines
2538 ...persistedDraft,2588 ...persistedDraft,
2539 status: "connected",2589 status: "connected",
2540 },2590 },
2541 actorAccountId: input.sourceInput.actorAccountId,2591 actorScopeId: input.sourceInput.actorScopeId,
2542 }),2592 }),
2543 );2593 );
2544 2594
@@ -2546,7 +2596,7 @@ const addExecutorGoogleDiscoverySource = (input: { · 1 unmodified line
2546 onLeft: (error) =>2596 onLeft: (error) =>
2547 isSourceCredentialRequiredError(error)2597 isSourceCredentialRequiredError(error)
2548 ? updateSourceStatus(input.sourceStore, persistedDraft, {2598 ? updateSourceStatus(input.sourceStore, persistedDraft, {
2549 actorAccountId: input.sourceInput.actorAccountId,2599 actorScopeId: input.sourceInput.actorScopeId,
2550 status: "auth_required",2600 status: "auth_required",
2551 lastError: null,2601 lastError: null,
2552 }).pipe(2602 }).pipe(
@@ -2559,7 +2609,7 @@ const addExecutorGoogleDiscoverySource = (input: { · 6 unmodified lines
2559 ),2609 ),
2560 )2610 )
2561 : updateSourceStatus(input.sourceStore, persistedDraft, {2611 : updateSourceStatus(input.sourceStore, persistedDraft, {
2562 actorAccountId: input.sourceInput.actorAccountId,2612 actorScopeId: input.sourceInput.actorScopeId,
2563 status: "error",2613 status: "error",
2564 lastError: error.message,2614 lastError: error.message,
2565 }).pipe(2615 }).pipe(
@@ -2567,7 +2617,7 @@ const addExecutorGoogleDiscoverySource = (input: { · 1 unmodified line
2567 ),2617 ),
2568 onRight: () =>2618 onRight: () =>
2569 updateSourceStatus(input.sourceStore, persistedDraft, {2619 updateSourceStatus(input.sourceStore, persistedDraft, {
2570 actorAccountId: input.sourceInput.actorAccountId,2620 actorScopeId: input.sourceInput.actorScopeId,
2571 status: "connected",2621 status: "connected",
2572 lastError: null,2622 lastError: null,
2573 }).pipe(2623 }).pipe(
@@ -2587,16 +2637,17 @@ const connectGoogleDiscoveryBatchInternal = (input: { · 13 unmodified lines
2587 sourceCatalogSync: RuntimeSourceCatalogSyncShape;2637 sourceCatalogSync: RuntimeSourceCatalogSyncShape;
2588 sourceInput: ConnectGoogleDiscoveryBatchInput;2638 sourceInput: ConnectGoogleDiscoveryBatchInput;
2589 getLocalServerBaseUrl?: () => string | undefined;2639 getLocalServerBaseUrl?: () => string | undefined;
2590}): Effect.Effect<ConnectGoogleDiscoveryBatchResult, Error, WorkspaceStorageServices> =>2640 localScopeState?: RuntimeLocalScopeState;
2641}): Effect.Effect<ConnectGoogleDiscoveryBatchResult, Error, ScopeStorageServices> =>
2591 Effect.gen(function* () {2642 Effect.gen(function* () {
2592 if (input.sourceInput.sources.length === 0) {2643 if (input.sourceInput.sources.length === 0) {
2593 return yield* runtimeEffectError("sources/source-auth-service", "Google batch connect requires at least one source");2644 return yield* runtimeEffectError("sources/source-auth-service", "Google batch connect requires at least one source");
2594 }2645 }
2595 2646
2596 const existingSources = yield* input.sourceStore.loadSourcesInWorkspace(2647 const existingSources = yield* input.sourceStore.loadSourcesInScope(
2597 input.sourceInput.workspaceId,2648 input.sourceInput.scopeId,
2598 {2649 {
2599 actorAccountId: input.sourceInput.actorAccountId,2650 actorScopeId: input.sourceInput.actorScopeId,
2600 },2651 },
2601 );2652 );
2602 2653
@@ -2664,7 +2715,7 @@ const connectGoogleDiscoveryBatchInternal = (input: { · 61 unmodified lines
2664 now,2715 now,
2665 })2716 })
2666 : yield* createSourceFromPayload({2717 : yield* createSourceFromPayload({
2667 workspaceId: input.sourceInput.workspaceId,2718 scopeId: input.sourceInput.scopeId,
2668 sourceId: SourceIdSchema.make(`src_${crypto.randomUUID()}`),2719 sourceId: SourceIdSchema.make(`src_${crypto.randomUUID()}`),
2669 payload: {2720 payload: {
2670 name: chosenName,2721 name: chosenName,
@@ -2688,7 +2739,7 @@ const connectGoogleDiscoveryBatchInternal = (input: { · 17 unmodified lines
2688 });2739 });
2689 2740
2690 const persistedDraft = yield* input.sourceStore.persistSource(draftSource, {2741 const persistedDraft = yield* input.sourceStore.persistSource(draftSource, {
2691 actorAccountId: input.sourceInput.actorAccountId,2742 actorScopeId: input.sourceInput.actorScopeId,
2692 });2743 });
2693 const providerAuthTarget = yield* resolveRuntimeProviderAuthTarget(persistedDraft);2744 const providerAuthTarget = yield* resolveRuntimeProviderAuthTarget(persistedDraft);
2694 if (providerAuthTarget === null) {2745 if (providerAuthTarget === null) {
@@ -2700,28 +2751,30 @@ const connectGoogleDiscoveryBatchInternal = (input: { · 5 unmodified lines
2700 { discard: false },2751 { discard: false },
2701 );2752 );
2702 2753
2703 const workspaceOauthClient = yield* resolveWorkspaceOauthClientById({2754 const scopeOauthClient = yield* resolveScopeOauthClientById({
2704 executorState: input.executorState,2755 executorState: input.executorState,
2705 workspaceId: input.sourceInput.workspaceId,2756 scopeId: input.sourceInput.scopeId,
2706 oauthClientId: input.sourceInput.workspaceOauthClientId,2757 oauthClientId: input.sourceInput.scopeOauthClientId,
2707 providerKey: persistedTargets[0]!.setupConfig.providerKey,2758 providerKey: persistedTargets[0]!.setupConfig.providerKey,
2759 localScopeState: input.localScopeState,
2708 });2760 });
2709 if (workspaceOauthClient === null) {2761 if (scopeOauthClient === null) {
2710 return yield* runtimeEffectError("sources/source-auth-service", `Workspace OAuth client not found: ${input.sourceInput.workspaceOauthClientId}`);2762 return yield* runtimeEffectError("sources/source-auth-service", `Scope OAuth client not found: ${input.sourceInput.scopeOauthClientId}`);
2711 }2763 }
2712 2764
2713 const providerAuthResult = yield* connectSourcesWithProviderRuntimeAuth({2765 const providerAuthResult = yield* connectSourcesWithProviderRuntimeAuth({
2714 executorState: input.executorState,2766 executorState: input.executorState,
2715 sourceStore: input.sourceStore,2767 sourceStore: input.sourceStore,
2716 sourceCatalogSync: input.sourceCatalogSync,2768 sourceCatalogSync: input.sourceCatalogSync,
2717 workspaceId: input.sourceInput.workspaceId,2769 scopeId: input.sourceInput.scopeId,
2718 actorAccountId: input.sourceInput.actorAccountId,2770 actorScopeId: input.sourceInput.actorScopeId,
2719 executionId: input.sourceInput.executionId,2771 executionId: input.sourceInput.executionId,
2720 interactionId: input.sourceInput.interactionId,2772 interactionId: input.sourceInput.interactionId,
2721 baseUrl: input.sourceInput.baseUrl,2773 baseUrl: input.sourceInput.baseUrl,
2722 getLocalServerBaseUrl: input.getLocalServerBaseUrl,2774 getLocalServerBaseUrl: input.getLocalServerBaseUrl,
2723 workspaceOauthClient,2775 scopeOauthClient,
2724 targets: persistedTargets,2776 targets: persistedTargets,
2777 localScopeState: input.localScopeState,
2725 });2778 });
2726 2779
2727 if (providerAuthResult.kind === "connected") {2780 if (providerAuthResult.kind === "connected") {
@@ -2749,10 +2802,10 @@ const connectGoogleDiscoveryBatchInternal = (input: { · 21 unmodified lines
2749 2802
2750type RuntimeSourceAuthServiceShape = {2803type RuntimeSourceAuthServiceShape = {
2751 getSourceById: (input: {2804 getSourceById: (input: {
2752 workspaceId: WorkspaceId;2805 scopeId: ScopeId;
2753 sourceId: Source["id"];2806 sourceId: Source["id"];
2754 actorAccountId?: AccountId | null;2807 actorScopeId?: ScopeId | null;
2755 }) => Effect.Effect<Source, Error, WorkspaceStorageServices>;2808 }) => Effect.Effect<Source, Error, ScopeStorageServices>;
2756 getLocalServerBaseUrl: () => string | null;2809 getLocalServerBaseUrl: () => string | null;
2757 storeSecretMaterial: (input: {2810 storeSecretMaterial: (input: {
2758 purpose: SecretMaterialPurpose;2811 purpose: SecretMaterialPurpose;
@@ -2764,28 +2817,28 @@ type RuntimeSourceAuthServiceShape = { · 5 unmodified lines
2764 mcpDiscoveryElicitation?: McpDiscoveryElicitationContext;2817 mcpDiscoveryElicitation?: McpDiscoveryElicitationContext;
2765 baseUrl?: string | null;2818 baseUrl?: string | null;
2766 },2819 },
2767 ) => Effect.Effect<ExecutorSourceAddResult, Error, WorkspaceStorageServices>;2820 ) => Effect.Effect<ExecutorSourceAddResult, Error, ScopeStorageServices>;
2768 connectGoogleDiscoveryBatch: (2821 connectGoogleDiscoveryBatch: (
2769 input: ConnectGoogleDiscoveryBatchInput,2822 input: ConnectGoogleDiscoveryBatchInput,
2770 ) => Effect.Effect<ConnectGoogleDiscoveryBatchResult, Error, WorkspaceStorageServices>;2823 ) => Effect.Effect<ConnectGoogleDiscoveryBatchResult, Error, ScopeStorageServices>;
2771 connectMcpSource: (2824 connectMcpSource: (
2772 input: ConnectMcpSourceInput,2825 input: ConnectMcpSourceInput,
2773 ) => Effect.Effect<McpSourceConnectResult, Error, WorkspaceStorageServices>;2826 ) => Effect.Effect<McpSourceConnectResult, Error, ScopeStorageServices>;
2774 listWorkspaceOauthClients: (input: {2827 listScopeOauthClients: (input: {
2775 workspaceId: WorkspaceId;2828 scopeId: ScopeId;
2776 providerKey: string;2829 providerKey: string;
2777 }) => Effect.Effect<readonly WorkspaceOauthClient[], Error, WorkspaceStorageServices>;2830 }) => Effect.Effect<readonly ScopeOauthClient[], Error, ScopeStorageServices>;
2778 createWorkspaceOauthClient: (2831 createScopeOauthClient: (
2779 input: CreateWorkspaceOauthClientInput,2832 input: CreateScopeOauthClientInput,
2780 ) => Effect.Effect<WorkspaceOauthClient, Error, WorkspaceStorageServices>;2833 ) => Effect.Effect<ScopeOauthClient, Error, ScopeStorageServices>;
2781 removeWorkspaceOauthClient: (input: {2834 removeScopeOauthClient: (input: {
2782 workspaceId: WorkspaceId;2835 scopeId: ScopeId;
2783 oauthClientId: WorkspaceOauthClient["id"];2836 oauthClientId: ScopeOauthClient["id"];
2784 }) => Effect.Effect<boolean, Error, WorkspaceStorageServices>;2837 }) => Effect.Effect<boolean, Error, ScopeStorageServices>;
2785 removeProviderAuthGrant: (input: {2838 removeProviderAuthGrant: (input: {
2786 workspaceId: WorkspaceId;2839 scopeId: ScopeId;
2787 grantId: ProviderAuthGrant["id"];2840 grantId: ProviderAuthGrant["id"];
2788 }) => Effect.Effect<boolean, Error, WorkspaceStorageServices>;2841 }) => Effect.Effect<boolean, Error, ScopeStorageServices>;
2789 startSourceOAuthSession: (2842 startSourceOAuthSession: (
2790 input: StartSourceOAuthSessionInput,2843 input: StartSourceOAuthSessionInput,
2791 ) => Effect.Effect<StartSourceOAuthSessionResult, Error, never>;2844 ) => Effect.Effect<StartSourceOAuthSessionResult, Error, never>;
@@ -2794,24 +2847,24 @@ type RuntimeSourceAuthServiceShape = { · 2 unmodified lines
2794 code?: string | null;2847 code?: string | null;
2795 error?: string | null;2848 error?: string | null;
2796 errorDescription?: string | null;2849 errorDescription?: string | null;
2797 }) => Effect.Effect<CompleteSourceOAuthSessionResult, Error, WorkspaceStorageServices>;2850 }) => Effect.Effect<CompleteSourceOAuthSessionResult, Error, ScopeStorageServices>;
2798 completeProviderOauthCallback: (input: {2851 completeProviderOauthCallback: (input: {
2799 workspaceId: WorkspaceId;2852 scopeId: ScopeId;
2800 actorAccountId?: AccountId | null;2853 actorScopeId?: ScopeId | null;
2801 state: string;2854 state: string;
2802 code?: string | null;2855 code?: string | null;
2803 error?: string | null;2856 error?: string | null;
2804 errorDescription?: string | null;2857 errorDescription?: string | null;
2805 }) => Effect.Effect<CompleteProviderOauthCallbackResult, Error, WorkspaceStorageServices>;2858 }) => Effect.Effect<CompleteProviderOauthCallbackResult, Error, ScopeStorageServices>;
2806 completeSourceCredentialSetup: (input: {2859 completeSourceCredentialSetup: (input: {
2807 workspaceId: WorkspaceId;2860 scopeId: ScopeId;
2808 sourceId: Source["id"];2861 sourceId: Source["id"];
2809 actorAccountId?: AccountId | null;2862 actorScopeId?: ScopeId | null;
2810 state: string;2863 state: string;
2811 code?: string | null;2864 code?: string | null;
2812 error?: string | null;2865 error?: string | null;
2813 errorDescription?: string | null;2866 errorDescription?: string | null;
2814 }) => Effect.Effect<CompleteSourceCredentialSetupResult, Error, WorkspaceStorageServices>;2867 }) => Effect.Effect<CompleteSourceCredentialSetupResult, Error, ScopeStorageServices>;
2815};2868};
2816 2869
2817type RuntimeSourceAuthDependencies = {2870type RuntimeSourceAuthDependencies = {
@@ -2823,10 +2876,10 @@ type RuntimeSourceAuthDependencies = { · 5 unmodified lines
2823 storeSecretMaterial: StoreSecretMaterial;2876 storeSecretMaterial: StoreSecretMaterial;
2824 deleteSecretMaterial: DeleteSecretMaterial;2877 deleteSecretMaterial: DeleteSecretMaterial;
2825 getLocalServerBaseUrl?: () => string | undefined;2878 getLocalServerBaseUrl?: () => string | undefined;
2826 localWorkspaceState?: RuntimeLocalWorkspaceState;2879 localScopeState?: RuntimeLocalScopeState;
2827};2880};
2828 2881
2829type ProvideLocalWorkspace = <A, E, R>(2882type ProvideLocalScope = <A, E, R>(
2830 effect: Effect.Effect<A, E, R>,2883 effect: Effect.Effect<A, E, R>,
2831) => Effect.Effect<A, E, R>;2884) => Effect.Effect<A, E, R>;
2832 2885
@@ -2836,9 +2889,9 @@ type RuntimeSourceConnectionServiceShape = Pick< · 3 unmodified lines
2836 | "addExecutorSource"2889 | "addExecutorSource"
2837 | "connectGoogleDiscoveryBatch"2890 | "connectGoogleDiscoveryBatch"
2838 | "connectMcpSource"2891 | "connectMcpSource"
2839 | "listWorkspaceOauthClients"2892 | "listScopeOauthClients"
2840 | "createWorkspaceOauthClient"2893 | "createScopeOauthClient"
2841 | "removeWorkspaceOauthClient"2894 | "removeScopeOauthClient"
2842 | "removeProviderAuthGrant"2895 | "removeProviderAuthGrant"
2843>;2896>;
2844 2897
@@ -2852,24 +2905,24 @@ type RuntimeSourceOAuthSessionServiceShape = Pick< · 7 unmodified lines
2852 2905
2853const createRuntimeSourceConnectionService = (2906const createRuntimeSourceConnectionService = (
2854 input: RuntimeSourceAuthDependencies,2907 input: RuntimeSourceAuthDependencies,
2855 provideLocalWorkspace: ProvideLocalWorkspace,2908 provideLocalWorkspace: ProvideLocalScope,
2856): RuntimeSourceConnectionServiceShape => {2909): RuntimeSourceConnectionServiceShape => {
2857 const mirrorLocalSourceResult = (2910 const mirrorLocalSourceResult = (
2858 result: ExecutorSourceAddResult,2911 result: ExecutorSourceAddResult,
2859 ): Effect.Effect<ExecutorSourceAddResult, Error, WorkspaceStorageServices> =>2912 ): Effect.Effect<ExecutorSourceAddResult, Error, ScopeStorageServices> =>
2860 Effect.succeed(result);2913 Effect.succeed(result);
2861 const mirrorLocalMcpSourceResult = (2914 const mirrorLocalMcpSourceResult = (
2862 result: McpSourceConnectResult,2915 result: McpSourceConnectResult,
2863 ): Effect.Effect<McpSourceConnectResult, Error, WorkspaceStorageServices> =>2916 ): Effect.Effect<McpSourceConnectResult, Error, ScopeStorageServices> =>
2864 Effect.succeed(result);2917 Effect.succeed(result);
2865 2918
2866 return {2919 return {
2867 getSourceById: ({ workspaceId, sourceId, actorAccountId }) =>2920 getSourceById: ({ scopeId, sourceId, actorScopeId }) =>
2868 provideLocalWorkspace(2921 provideLocalWorkspace(
2869 input.sourceStore.loadSourceById({2922 input.sourceStore.loadSourceById({
2870 workspaceId,2923 scopeId,
2871 sourceId,2924 sourceId,
2872 actorAccountId,2925 actorScopeId,
2873 }),2926 }),
2874 ),2927 ),
2875 2928
@@ -2886,6 +2939,7 @@ const createRuntimeSourceConnectionService = ( · 10 unmodified lines
2886 resolveSecretMaterial: input.resolveSecretMaterial,2939 resolveSecretMaterial: input.resolveSecretMaterial,
2887 getLocalServerBaseUrl: input.getLocalServerBaseUrl,2940 getLocalServerBaseUrl: input.getLocalServerBaseUrl,
2888 baseUrl: options?.baseUrl,2941 baseUrl: options?.baseUrl,
2942 localScopeState: input.localScopeState,
2889 })2943 })
2890 : isExecutorHttpEndpointSourceInput(sourceInput)2944 : isExecutorHttpEndpointSourceInput(sourceInput)
2891 ? addExecutorHttpSource({2945 ? addExecutorHttpSource({
@@ -2898,6 +2952,7 @@ const createRuntimeSourceConnectionService = ( · 6 unmodified lines
2898 resolveSecretMaterial: input.resolveSecretMaterial,2952 resolveSecretMaterial: input.resolveSecretMaterial,
2899 getLocalServerBaseUrl: input.getLocalServerBaseUrl,2953 getLocalServerBaseUrl: input.getLocalServerBaseUrl,
2900 baseUrl: options?.baseUrl,2954 baseUrl: options?.baseUrl,
2955 localScopeState: input.localScopeState,
2901 })2956 })
2902 : isExecutorMcpSourceInput(sourceInput)2957 : isExecutorMcpSourceInput(sourceInput)
2903 ? connectMcpSourceInternal({2958 ? connectMcpSourceInternal({
@@ -2905,8 +2960,8 @@ const createRuntimeSourceConnectionService = ( · 1 unmodified line
2905 sourceStore: input.sourceStore,2960 sourceStore: input.sourceStore,
2906 sourceCatalogSync: input.sourceCatalogSync,2961 sourceCatalogSync: input.sourceCatalogSync,
2907 getLocalServerBaseUrl: input.getLocalServerBaseUrl,2962 getLocalServerBaseUrl: input.getLocalServerBaseUrl,
2908 workspaceId: sourceInput.workspaceId,2963 scopeId: sourceInput.scopeId,
2909 actorAccountId: sourceInput.actorAccountId,2964 actorScopeId: sourceInput.actorScopeId,
2910 executionId: sourceInput.executionId,2965 executionId: sourceInput.executionId,
2911 interactionId: sourceInput.interactionId,2966 interactionId: sourceInput.interactionId,
2912 endpoint: sourceInput.endpoint,2967 endpoint: sourceInput.endpoint,
@@ -2936,6 +2991,7 @@ const createRuntimeSourceConnectionService = ( · 23 unmodified lines
2936 sourceCatalogSync: input.sourceCatalogSync,2991 sourceCatalogSync: input.sourceCatalogSync,
2937 sourceInput,2992 sourceInput,
2938 getLocalServerBaseUrl: input.getLocalServerBaseUrl,2993 getLocalServerBaseUrl: input.getLocalServerBaseUrl,
2994 localScopeState: input.localScopeState,
2939 }),2995 }),
2940 ),2996 ),
2941 2997
@@ -2946,8 +3002,8 @@ const createRuntimeSourceConnectionService = ( · 4 unmodified lines
2946 sourceStore: input.sourceStore,3002 sourceStore: input.sourceStore,
2947 sourceCatalogSync: input.sourceCatalogSync,3003 sourceCatalogSync: input.sourceCatalogSync,
2948 getLocalServerBaseUrl: input.getLocalServerBaseUrl,3004 getLocalServerBaseUrl: input.getLocalServerBaseUrl,
2949 workspaceId: sourceInput.workspaceId,3005 scopeId: sourceInput.scopeId,
2950 actorAccountId: sourceInput.actorAccountId,3006 actorScopeId: sourceInput.actorScopeId,
2951 sourceId: sourceInput.sourceId,3007 sourceId: sourceInput.sourceId,
2952 executionId: null,3008 executionId: null,
2953 interactionId: null,3009 interactionId: null,
@@ -2969,54 +3025,71 @@ const createRuntimeSourceConnectionService = ( · 15 unmodified lines
2969 ),3025 ),
2970 ),3026 ),
2971 3027
2972 listWorkspaceOauthClients: ({ workspaceId, providerKey }) =>3028 listScopeOauthClients: ({ scopeId, providerKey }) =>
2973 provideLocalWorkspace(3029 provideLocalWorkspace(
2974 input.executorState.workspaceOauthClients.listByWorkspaceAndProvider({3030 Effect.gen(function* () {
2975 workspaceId,3031 const scopeIds = resolvedScopeSearchOrder({
2976 providerKey,3032 scopeId,
3033 localScopeState: input.localScopeState,
3034 });
3035 const clientsById = new Map<ScopeOauthClient["id"], ScopeOauthClient>();
3036
3037 for (const candidateScopeId of scopeIds) {
3038 const clients = yield* input.executorState.scopeOauthClients.listByScopeAndProvider({
3039 scopeId: candidateScopeId,
3040 providerKey,
3041 });
3042 for (const client of clients) {
3043 if (!clientsById.has(client.id)) {
3044 clientsById.set(client.id, client);
3045 }
3046 }
3047 }
3048
3049 return [...clientsById.values()];
2977 }),3050 }),
2978 ),3051 ),
2979 3052
2980 createWorkspaceOauthClient: ({ workspaceId, providerKey, label, oauthClient }) =>3053 createScopeOauthClient: ({ scopeId, providerKey, label, oauthClient }) =>
2981 provideLocalWorkspace(3054 provideLocalWorkspace(
2982 Effect.gen(function* () {3055 Effect.gen(function* () {
2983 const sourceAdapter = findSourceAdapterByProviderKey(providerKey);3056 const sourceAdapter = findSourceAdapterByProviderKey(providerKey);
2984 const created = yield* createWorkspaceOauthClient({3057 const created = yield* createScopeOauthClient({
2985 executorState: input.executorState,3058 executorState: input.executorState,
2986 workspaceId,3059 scopeId,
2987 providerKey,3060 providerKey,
2988 oauthClient,3061 oauthClient,
2989 label,3062 label,
2990 normalizeOauthClient: sourceAdapter?.normalizeOauthClientInput,3063 normalizeOauthClient: sourceAdapter?.normalizeOauthClientInput,
2991 storeSecretMaterial: input.storeSecretMaterial,3064 storeSecretMaterial: input.storeSecretMaterial,
2992 });3065 });
2993 const stored = yield* input.executorState.workspaceOauthClients.getById(created.id);3066 const stored = yield* input.executorState.scopeOauthClients.getById(created.id);
2994 if (Option.isNone(stored)) {3067 if (Option.isNone(stored)) {
2995 return yield* runtimeEffectError("sources/source-auth-service", `Workspace OAuth client ${created.id} was not persisted`);3068 return yield* runtimeEffectError("sources/source-auth-service", `Scope OAuth client ${created.id} was not persisted`);
2996 }3069 }
2997 3070
2998 return stored.value;3071 return stored.value;
2999 }),3072 }),
3000 ),3073 ),
3001 3074
3002 removeWorkspaceOauthClient: ({ workspaceId, oauthClientId }) =>3075 removeScopeOauthClient: ({ scopeId, oauthClientId }) =>
3003 provideLocalWorkspace(3076 provideLocalWorkspace(
3004 Effect.gen(function* () {3077 Effect.gen(function* () {
3005 const oauthClient = yield* input.executorState.workspaceOauthClients.getById(oauthClientId);3078 const oauthClient = yield* input.executorState.scopeOauthClients.getById(oauthClientId);
3006 if (Option.isNone(oauthClient) || oauthClient.value.workspaceId !== workspaceId) {3079 if (Option.isNone(oauthClient) || oauthClient.value.scopeId !== scopeId) {
3007 return false;3080 return false;
3008 }3081 }
3009 3082
3010 const grants = yield* input.executorState.providerAuthGrants.listByWorkspaceId(workspaceId);3083 const grants = yield* input.executorState.providerAuthGrants.listByScopeId(scopeId);
3011 const dependentGrant = grants.find((grant) => grant.oauthClientId === oauthClientId);3084 const dependentGrant = grants.find((grant) => grant.oauthClientId === oauthClientId);
3012 if (dependentGrant) {3085 if (dependentGrant) {
3013 return yield* runtimeEffectError("sources/source-auth-service", 3086 return yield* runtimeEffectError("sources/source-auth-service",
3014 `Workspace OAuth client ${oauthClientId} is still referenced by provider grant ${dependentGrant.id}`,3087 `Scope OAuth client ${oauthClientId} is still referenced by provider grant ${dependentGrant.id}`,
3015 );3088 );
3016 }3089 }
3017 3090
3018 const secretRef = sourceOauthClientSecretRef(oauthClient.value);3091 const secretRef = sourceOauthClientSecretRef(oauthClient.value);
3019 const removed = yield* input.executorState.workspaceOauthClients.removeById(oauthClientId);3092 const removed = yield* input.executorState.scopeOauthClients.removeById(oauthClientId);
3020 if (removed && secretRef) {3093 if (removed && secretRef) {
3021 yield* input.deleteSecretMaterial(secretRef).pipe(3094 yield* input.deleteSecretMaterial(secretRef).pipe(
3022 Effect.either,3095 Effect.either,
@@ -3028,12 +3101,12 @@ const createRuntimeSourceConnectionService = ( · 5 unmodified lines
3028 }),3101 }),
3029 ),3102 ),
3030 3103
3031 removeProviderAuthGrant: ({ workspaceId, grantId }) =>3104 removeProviderAuthGrant: ({ scopeId, grantId }) =>
3032 provideLocalWorkspace(3105 provideLocalWorkspace(
3033 removeProviderAuthGrantInternal({3106 removeProviderAuthGrantInternal({
3034 executorState: input.executorState,3107 executorState: input.executorState,
3035 sourceStore: input.sourceStore,3108 sourceStore: input.sourceStore,
3036 workspaceId,3109 scopeId,
3037 grantId,3110 grantId,
3038 deleteSecretMaterial: input.deleteSecretMaterial,3111 deleteSecretMaterial: input.deleteSecretMaterial,
3039 }),3112 }),
@@ -3043,7 +3116,7 @@ const createRuntimeSourceConnectionService = ( · 3 unmodified lines
3043 3116
3044const createRuntimeSourceOAuthSessionService = (3117const createRuntimeSourceOAuthSessionService = (
3045 input: RuntimeSourceAuthDependencies,3118 input: RuntimeSourceAuthDependencies,
3046 provideLocalWorkspace: ProvideLocalWorkspace,3119 provideLocalWorkspace: ProvideLocalScope,
3047): RuntimeSourceOAuthSessionServiceShape => ({3120): RuntimeSourceOAuthSessionServiceShape => ({
3048 startSourceOAuthSession: (oauthInput) =>3121 startSourceOAuthSession: (oauthInput) =>
3049 Effect.gen(function* () {3122 Effect.gen(function* () {
@@ -3069,9 +3142,9 @@ const createRuntimeSourceOAuthSessionService = ( · 19 unmodified lines
3069 3142
3070 yield* input.executorState.sourceAuthSessions.upsert({3143 yield* input.executorState.sourceAuthSessions.upsert({
3071 id: sessionId,3144 id: sessionId,
3072 workspaceId: oauthInput.workspaceId,3145 scopeId: oauthInput.scopeId,
3073 sourceId: SourceIdSchema.make(`oauth_draft_${crypto.randomUUID()}`),3146 sourceId: SourceIdSchema.make(`oauth_draft_${crypto.randomUUID()}`),
3074 actorAccountId: oauthInput.actorAccountId ?? null,3147 actorScopeId: oauthInput.actorScopeId ?? null,
3075 credentialSlot: "runtime",3148 credentialSlot: "runtime",
3076 executionId: null,3149 executionId: null,
3077 interactionId: null,3150 interactionId: null,
@@ -3215,7 +3288,7 @@ const createRuntimeSourceOAuthSessionService = ( · 137 unmodified lines
3215 } satisfies CompleteSourceOAuthSessionResult;3288 } satisfies CompleteSourceOAuthSessionResult;
3216 })),3289 })),
3217 3290
3218 completeProviderOauthCallback: ({ workspaceId, actorAccountId, state, code, error, errorDescription }) =>3291 completeProviderOauthCallback: ({ scopeId, actorScopeId, state, code, error, errorDescription }) =>
3219 provideLocalWorkspace(Effect.gen(function* () {3292 provideLocalWorkspace(Effect.gen(function* () {
3220 const sessionOption = yield* input.executorState.sourceAuthSessions.getByState(state);3293 const sessionOption = yield* input.executorState.sourceAuthSessions.getByState(state);
3221 if (Option.isNone(sessionOption)) {3294 if (Option.isNone(sessionOption)) {
@@ -3223,10 +3296,10 @@ const createRuntimeSourceOAuthSessionService = ( · 1 unmodified line
3223 }3296 }
3224 3297
3225 const session = sessionOption.value;3298 const session = sessionOption.value;
3226 if (session.workspaceId !== workspaceId) {3299 if (session.scopeId !== scopeId) {
3227 return yield* runtimeEffectError("sources/source-auth-service", `Source auth session ${session.id} does not match workspaceId=${workspaceId}`);3300 return yield* runtimeEffectError("sources/source-auth-service", `Source auth session ${session.id} does not match scopeId=${scopeId}`);
3228 }3301 }
3229 if ((session.actorAccountId ?? null) !== (actorAccountId ?? null)) {3302 if ((session.actorScopeId ?? null) !== (actorScopeId ?? null)) {
3230 return yield* runtimeEffectError("sources/source-auth-service", `Source auth session ${session.id} does not match the active account`);3303 return yield* runtimeEffectError("sources/source-auth-service", `Source auth session ${session.id} does not match the active account`);
3231 }3304 }
3232 if (session.providerKind !== "oauth2_provider_batch") {3305 if (session.providerKind !== "oauth2_provider_batch") {
@@ -3238,9 +3311,9 @@ const createRuntimeSourceOAuthSessionService = ( · 5 unmodified lines
3238 sessionData.targetSources,3311 sessionData.targetSources,
3239 (target) =>3312 (target) =>
3240 input.sourceStore.loadSourceById({3313 input.sourceStore.loadSourceById({
3241 workspaceId,3314 scopeId,
3242 sourceId: target.sourceId,3315 sourceId: target.sourceId,
3243 actorAccountId,3316 actorScopeId,
3244 }),3317 }),
3245 { discard: false },3318 { discard: false },
3246 );3319 );
@@ -3276,14 +3349,15 @@ const createRuntimeSourceOAuthSessionService = ( · 29 unmodified lines
3276 return yield* runtimeEffectError("sources/source-auth-service", "OAuth session is missing the PKCE code verifier");3349 return yield* runtimeEffectError("sources/source-auth-service", "OAuth session is missing the PKCE code verifier");
3277 }3350 }
3278 3351
3279 const oauthClient = yield* resolveWorkspaceOauthClientById({3352 const oauthClient = yield* resolveScopeOauthClientById({
3280 executorState: input.executorState,3353 executorState: input.executorState,
3281 workspaceId,3354 scopeId,
3282 oauthClientId: sessionData.oauthClientId,3355 oauthClientId: sessionData.oauthClientId,
3283 providerKey: sessionData.providerKey,3356 providerKey: sessionData.providerKey,
3357 localScopeState: input.localScopeState,
3284 });3358 });
3285 if (oauthClient === null) {3359 if (oauthClient === null) {
3286 return yield* runtimeEffectError("sources/source-auth-service", `Workspace OAuth client not found: ${sessionData.oauthClientId}`);3360 return yield* runtimeEffectError("sources/source-auth-service", `Scope OAuth client not found: ${sessionData.oauthClientId}`);
3287 }3361 }
3288 3362
3289 let clientSecret: string | null = null;3363 let clientSecret: string | null = null;
@@ -3303,9 +3377,9 @@ const createRuntimeSourceOAuthSessionService = ( · 13 unmodified lines
3303 code: authorizationCode,3377 code: authorizationCode,
3304 });3378 });
3305 3379
3306 const availableGrants = yield* input.executorState.providerAuthGrants.listByWorkspaceActorAndProvider({3380 const availableGrants = yield* input.executorState.providerAuthGrants.listByScopeActorAndProvider({
3307 workspaceId,3381 scopeId,
3308 actorAccountId: actorAccountId ?? null,3382 actorScopeId: actorScopeId ?? null,
3309 providerKey: sessionData.providerKey,3383 providerKey: sessionData.providerKey,
3310 });3384 });
3311 const existingGrant = availableGrants.find(3385 const existingGrant = availableGrants.find(
@@ -3319,8 +3393,8 @@ const createRuntimeSourceOAuthSessionService = ( · 7 unmodified lines
3319 );3393 );
3320 const nextGrant = yield* upsertProviderAuthGrant({3394 const nextGrant = yield* upsertProviderAuthGrant({
3321 executorState: input.executorState,3395 executorState: input.executorState,
3322 workspaceId,3396 scopeId,
3323 actorAccountId,3397 actorScopeId,
3324 providerKey: sessionData.providerKey,3398 providerKey: sessionData.providerKey,
3325 oauthClientId: sessionData.oauthClientId,3399 oauthClientId: sessionData.oauthClientId,
3326 tokenEndpoint: sessionData.tokenEndpoint,3400 tokenEndpoint: sessionData.tokenEndpoint,
@@ -3339,9 +3413,9 @@ const createRuntimeSourceOAuthSessionService = ( · 12 unmodified lines
3339 (target) =>3413 (target) =>
3340 Effect.map(3414 Effect.map(
3341 input.sourceStore.loadSourceById({3415 input.sourceStore.loadSourceById({
3342 workspaceId,3416 scopeId,
3343 sourceId: target.sourceId,3417 sourceId: target.sourceId,
3344 actorAccountId,3418 actorScopeId,
3345 }),3419 }),
3346 (source) => ({3420 (source) => ({
3347 source,3421 source,
@@ -3354,7 +3428,7 @@ const createRuntimeSourceOAuthSessionService = ( · 6 unmodified lines
3354 const connectedSources = yield* attachProviderGrantToSources({3428 const connectedSources = yield* attachProviderGrantToSources({
3355 sourceStore: input.sourceStore,3429 sourceStore: input.sourceStore,
3356 sourceCatalogSync: input.sourceCatalogSync,3430 sourceCatalogSync: input.sourceCatalogSync,
3357 actorAccountId,3431 actorScopeId,
3358 grantId: nextGrant.id,3432 grantId: nextGrant.id,
3359 providerKey: nextGrant.providerKey,3433 providerKey: nextGrant.providerKey,
3360 headerName: nextGrant.headerName,3434 headerName: nextGrant.headerName,
@@ -3384,7 +3458,7 @@ const createRuntimeSourceOAuthSessionService = ( · 23 unmodified lines
3384 } satisfies CompleteProviderOauthCallbackResult;3458 } satisfies CompleteProviderOauthCallbackResult;
3385 })),3459 })),
3386 3460
3387 completeSourceCredentialSetup: ({ workspaceId, sourceId, actorAccountId, state, code, error, errorDescription }) =>3461 completeSourceCredentialSetup: ({ scopeId, sourceId, actorScopeId, state, code, error, errorDescription }) =>
3388 provideLocalWorkspace(Effect.gen(function* () {3462 provideLocalWorkspace(Effect.gen(function* () {
3389 const sessionOption = yield* input.executorState.sourceAuthSessions.getByState(state);3463 const sessionOption = yield* input.executorState.sourceAuthSessions.getByState(state);
3390 if (Option.isNone(sessionOption)) {3464 if (Option.isNone(sessionOption)) {
@@ -3392,22 +3466,22 @@ const createRuntimeSourceOAuthSessionService = ( · 1 unmodified line
3392 }3466 }
3393 3467
3394 const session = sessionOption.value;3468 const session = sessionOption.value;
3395 if (session.workspaceId !== workspaceId || session.sourceId !== sourceId) {3469 if (session.scopeId !== scopeId || session.sourceId !== sourceId) {
3396 return yield* runtimeEffectError("sources/source-auth-service", 3470 return yield* runtimeEffectError("sources/source-auth-service",
3397 `Source auth session ${session.id} does not match workspaceId=${workspaceId} sourceId=${sourceId}`,3471 `Source auth session ${session.id} does not match scopeId=${scopeId} sourceId=${sourceId}`,
3398 );3472 );
3399 }3473 }
3400 if (3474 if (
3401 actorAccountId !== undefined3475 actorScopeId !== undefined
3402 && (session.actorAccountId ?? null) !== (actorAccountId ?? null)3476 && (session.actorScopeId ?? null) !== (actorScopeId ?? null)
3403 ) {3477 ) {
3404 return yield* runtimeEffectError("sources/source-auth-service", `Source auth session ${session.id} does not match the active account`);3478 return yield* runtimeEffectError("sources/source-auth-service", `Source auth session ${session.id} does not match the active account`);
3405 }3479 }
3406 3480
3407 const source = yield* input.sourceStore.loadSourceById({3481 const source = yield* input.sourceStore.loadSourceById({
3408 workspaceId: session.workspaceId,3482 scopeId: session.scopeId,
3409 sourceId: session.sourceId,3483 sourceId: session.sourceId,
3410 actorAccountId: session.actorAccountId,3484 actorScopeId: session.actorScopeId,
3411 });3485 });
3412 3486
3413 if (session.status === "completed") {3487 if (session.status === "completed") {
@@ -3439,13 +3513,13 @@ const createRuntimeSourceOAuthSessionService = ( · 25 unmodified lines
3439 }),3513 }),
3440 );3514 );
3441 const failedSource = yield* updateSourceStatus(input.sourceStore, source, {3515 const failedSource = yield* updateSourceStatus(input.sourceStore, source, {
3442 actorAccountId: session.actorAccountId,3516 actorScopeId: session.actorScopeId,
3443 status: "error",3517 status: "error",
3444 lastError: reason,3518 lastError: reason,
3445 });3519 });
3446 yield* input.sourceCatalogSync.sync({3520 yield* input.sourceCatalogSync.sync({
3447 source: failedSource,3521 source: failedSource,
3448 actorAccountId: session.actorAccountId,3522 actorScopeId: session.actorScopeId,
3449 });3523 });
3450 yield* completeLiveInteraction({3524 yield* completeLiveInteraction({
3451 executorState: input.executorState,3525 executorState: input.executorState,
@@ -3503,7 +3577,7 @@ const createRuntimeSourceOAuthSessionService = ( · 51 unmodified lines
3503 ? exchanged.scope!.split(/\s+/).filter((scope) => scope.length > 0)3577 ? exchanged.scope!.split(/\s+/).filter((scope) => scope.length > 0)
3504 : [...oauthSessionData.scopes];3578 : [...oauthSessionData.scopes];
3505 connectedSource = yield* updateSourceStatus(input.sourceStore, source, {3579 connectedSource = yield* updateSourceStatus(input.sourceStore, source, {
3506 actorAccountId: session.actorAccountId,3580 actorScopeId: session.actorScopeId,
3507 status: "connected",3581 status: "connected",
3508 lastError: null,3582 lastError: null,
3509 auth: {3583 auth: {
@@ -3518,10 +3592,10 @@ const createRuntimeSourceOAuthSessionService = ( · 8 unmodified lines
3518 grantSet: grantedScopes,3592 grantSet: grantedScopes,
3519 },3593 },
3520 });3594 });
3521 const authArtifact = yield* input.executorState.authArtifacts.getByWorkspaceSourceAndActor({3595 const authArtifact = yield* input.executorState.authArtifacts.getByScopeSourceAndActor({
3522 workspaceId: connectedSource.workspaceId,3596 scopeId: connectedSource.scopeId,
3523 sourceId: connectedSource.id,3597 sourceId: connectedSource.id,
3524 actorAccountId: session.actorAccountId ?? null,3598 actorScopeId: session.actorScopeId ?? null,
3525 slot: "runtime",3599 slot: "runtime",
3526 });3600 });
3527 if (Option.isSome(authArtifact)) {3601 if (Option.isSome(authArtifact)) {
@@ -3583,7 +3657,7 @@ const createRuntimeSourceOAuthSessionService = ( · 55 unmodified lines
3583 : null;3657 : null;
3584 3658
3585 connectedSource = yield* updateSourceStatus(input.sourceStore, source, {3659 connectedSource = yield* updateSourceStatus(input.sourceStore, source, {
3586 actorAccountId: session.actorAccountId,3660 actorScopeId: session.actorScopeId,
3587 status: "connected",3661 status: "connected",
3588 lastError: null,3662 lastError: null,
3589 auth: createPersistedMcpOAuthSourceAuth({3663 auth: createPersistedMcpOAuthSourceAuth({
@@ -3628,13 +3702,13 @@ const createRuntimeSourceOAuthSessionService = ( · 38 unmodified lines
3628 const indexed = yield* Effect.either(3702 const indexed = yield* Effect.either(
3629 input.sourceCatalogSync.sync({3703 input.sourceCatalogSync.sync({
3630 source: connectedSource,3704 source: connectedSource,
3631 actorAccountId: session.actorAccountId,3705 actorScopeId: session.actorScopeId,
3632 }),3706 }),
3633 );3707 );
3634 yield* Either.match(indexed, {3708 yield* Either.match(indexed, {
3635 onLeft: (error) =>3709 onLeft: (error) =>
3636 updateSourceStatus(input.sourceStore, connectedSource, {3710 updateSourceStatus(input.sourceStore, connectedSource, {
3637 actorAccountId: session.actorAccountId,3711 actorScopeId: session.actorScopeId,
3638 status: "error",3712 status: "error",
3639 lastError: error.message,3713 lastError: error.message,
3640 }).pipe(3714 }).pipe(
@@ -3660,8 +3734,8 @@ const createRuntimeSourceOAuthSessionService = ( · 19 unmodified lines
3660});3734});
3661 3735
3662export const createRuntimeSourceAuthService = (input: RuntimeSourceAuthDependencies) => {3736export const createRuntimeSourceAuthService = (input: RuntimeSourceAuthDependencies) => {
3663 const provideLocalWorkspace: ProvideLocalWorkspace = (effect) =>3737 const provideLocalWorkspace: ProvideLocalScope = (effect) =>
3664 provideOptionalRuntimeLocalWorkspace(effect, input.localWorkspaceState);3738 provideOptionalRuntimeLocalScope(effect, input.localScopeState);
3665 const sourceConnection = createRuntimeSourceConnectionService(3739 const sourceConnection = createRuntimeSourceConnectionService(
3666 input,3740 input,
3667 provideLocalWorkspace,3741 provideLocalWorkspace,
@@ -3704,7 +3778,7 @@ export const RuntimeSourceAuthServiceLive = (input: { · 36 unmodified lines
3704 const resolveSecretMaterial = yield* SecretMaterialResolverService;3778 const resolveSecretMaterial = yield* SecretMaterialResolverService;
3705 const storeSecretMaterial = yield* SecretMaterialStorerService;3779 const storeSecretMaterial = yield* SecretMaterialStorerService;
3706 const deleteSecretMaterial = yield* SecretMaterialDeleterService;3780 const deleteSecretMaterial = yield* SecretMaterialDeleterService;
3707 const runtimeLocalWorkspace = yield* getRuntimeLocalWorkspaceOption();3781 const runtimeLocalScope = yield* getRuntimeLocalScopeOption();
3708 3782
3709 return createRuntimeSourceAuthService({3783 return createRuntimeSourceAuthService({
3710 executorState,3784 executorState,
@@ -3715,7 +3789,7 @@ export const RuntimeSourceAuthServiceLive = (input: { · 4 unmodified lines
3715 storeSecretMaterial,3789 storeSecretMaterial,
3716 deleteSecretMaterial,3790 deleteSecretMaterial,
3717 getLocalServerBaseUrl: input.getLocalServerBaseUrl,3791 getLocalServerBaseUrl: input.getLocalServerBaseUrl,
3718 localWorkspaceState: runtimeLocalWorkspace ?? undefined,3792 localScopeState: runtimeLocalScope ?? undefined,
3719 });3793 });
3720 }),3794 }),
3721 );3795 );
packages/platform/sdk/src/runtime/sources/source-credential-interactions.ts+4−1
@@ -1,4 +1,7 @@
1import { SecretRefSchema, type SecretRef } from "#schema";1import {
2 SecretRefSchema,
3 type SecretRef,
4} from "#schema";
2import * as Schema from "effect/Schema";5import * as Schema from "effect/Schema";
3 6
4export const SourceCredentialSelectionContentSchema = Schema.Union(7export const SourceCredentialSelectionContentSchema = Schema.Union(
packages/platform/sdk/src/runtime/sources/source-definitions.test.ts+20−14
@@ -1,11 +1,15 @@
1import { describe, expect, it } from "@effect/vitest";1import {
2 describe,
3 expect,
4 it,
5} from "@effect/vitest";
2import * as Effect from "effect/Effect";6import * as Effect from "effect/Effect";
3 7
4import {8import {
5 CredentialIdSchema,9 CredentialIdSchema,
6 decodeBuiltInAuthArtifactConfig,10 decodeBuiltInAuthArtifactConfig,
7 SourceIdSchema,11 SourceIdSchema,
8 WorkspaceIdSchema,12 ScopeIdSchema,
9 type Source,13 type Source,
10} from "#schema";14} from "#schema";
11 15
@@ -17,7 +21,9 @@ import { · 5 unmodified lines
17 stableSourceCatalogRevisionId,21 stableSourceCatalogRevisionId,
18 updateSourceFromPayload,22 updateSourceFromPayload,
19} from "./source-definitions";23} from "./source-definitions";
20import { namespaceFromSourceName } from "./source-names";24import {
25 namespaceFromSourceName,
26} from "./source-names";
21 27
22const openApiBinding = (28const openApiBinding = (
23 specUrl = "https://api.github.com/openapi.json",29 specUrl = "https://api.github.com/openapi.json",
@@ -81,7 +87,7 @@ const mcpBinding = ( · 57 unmodified lines
81 87
82const makeSource = (overrides: Partial<Source> = {}): Source => ({88const makeSource = (overrides: Partial<Source> = {}): Source => ({
83 id: SourceIdSchema.make("src_source_definitions"),89 id: SourceIdSchema.make("src_source_definitions"),
84 workspaceId: WorkspaceIdSchema.make("ws_source_definitions"),90 scopeId: ScopeIdSchema.make("ws_source_definitions"),
85 name: "GitHub",91 name: "GitHub",
86 kind: "openapi",92 kind: "openapi",
87 endpoint: "https://api.github.com",93 endpoint: "https://api.github.com",
@@ -108,7 +114,7 @@ describe("source-definitions", () => { · 20 unmodified lines
108 name: "Renamed GitHub",114 name: "Renamed GitHub",
109 });115 });
110 const differentWorkspace = makeSource({116 const differentWorkspace = makeSource({
111 workspaceId: WorkspaceIdSchema.make("ws_source_definitions_other"),117 scopeId: ScopeIdSchema.make("ws_source_definitions_other"),
112 });118 });
113 119
114 expect(stableSourceCatalogId(source)).toBe(stableSourceCatalogId(source));120 expect(stableSourceCatalogId(source)).toBe(stableSourceCatalogId(source));
@@ -151,7 +157,7 @@ describe("source-definitions", () => { · 36 unmodified lines
151 () =>157 () =>
152 Effect.gen(function* () {158 Effect.gen(function* () {
153 const source = yield* createSourceFromPayload({159 const source = yield* createSourceFromPayload({
154 workspaceId: WorkspaceIdSchema.make("ws_create_defaults"),160 scopeId: ScopeIdSchema.make("ws_create_defaults"),
155 sourceId: SourceIdSchema.make("src_create_defaults"),161 sourceId: SourceIdSchema.make("src_create_defaults"),
156 payload: {162 payload: {
157 name: " GitHub ",163 name: " GitHub ",
@@ -216,7 +222,7 @@ describe("source-definitions", () => { · 58 unmodified lines
216 () =>222 () =>
217 Effect.gen(function* () {223 Effect.gen(function* () {
218 const source = yield* createSourceFromPayload({224 const source = yield* createSourceFromPayload({
219 workspaceId: WorkspaceIdSchema.make("ws_create_oauth_defaults"),225 scopeId: ScopeIdSchema.make("ws_create_oauth_defaults"),
220 sourceId: SourceIdSchema.make("src_create_oauth_defaults"),226 sourceId: SourceIdSchema.make("src_create_oauth_defaults"),
221 payload: {227 payload: {
222 name: "GraphQL Demo",228 name: "GraphQL Demo",
@@ -253,7 +259,7 @@ describe("source-definitions", () => { · 30 unmodified lines
253 Effect.gen(function* () {259 Effect.gen(function* () {
254 const invalidBearer = yield* Effect.flip(260 const invalidBearer = yield* Effect.flip(
255 createSourceFromPayload({261 createSourceFromPayload({
256 workspaceId: WorkspaceIdSchema.make("ws_invalid_bearer"),262 scopeId: ScopeIdSchema.make("ws_invalid_bearer"),
257 sourceId: SourceIdSchema.make("src_invalid_bearer"),263 sourceId: SourceIdSchema.make("src_invalid_bearer"),
258 payload: {264 payload: {
259 name: "Bad Bearer",265 name: "Bad Bearer",
@@ -279,7 +285,7 @@ describe("source-definitions", () => { · 19 unmodified lines
279 285
280 const invalidRefresh = yield* Effect.flip(286 const invalidRefresh = yield* Effect.flip(
281 createSourceFromPayload({287 createSourceFromPayload({
282 workspaceId: WorkspaceIdSchema.make("ws_invalid_refresh"),288 scopeId: ScopeIdSchema.make("ws_invalid_refresh"),
283 sourceId: SourceIdSchema.make("src_invalid_refresh"),289 sourceId: SourceIdSchema.make("src_invalid_refresh"),
284 payload: {290 payload: {
285 name: "Bad OAuth",291 name: "Bad OAuth",
@@ -313,7 +319,7 @@ describe("source-definitions", () => { · 27 unmodified lines
313 Effect.gen(function* () {319 Effect.gen(function* () {
314 const invalidMcp = yield* Effect.flip(320 const invalidMcp = yield* Effect.flip(
315 createSourceFromPayload({321 createSourceFromPayload({
316 workspaceId: WorkspaceIdSchema.make("ws_invalid_mcp"),322 scopeId: ScopeIdSchema.make("ws_invalid_mcp"),
317 sourceId: SourceIdSchema.make("src_invalid_mcp"),323 sourceId: SourceIdSchema.make("src_invalid_mcp"),
318 payload: {324 payload: {
319 name: "MCP",325 name: "MCP",
@@ -332,7 +338,7 @@ describe("source-definitions", () => { · 12 unmodified lines
332 338
333 const invalidOpenApiSpec = yield* Effect.flip(339 const invalidOpenApiSpec = yield* Effect.flip(
334 createSourceFromPayload({340 createSourceFromPayload({
335 workspaceId: WorkspaceIdSchema.make("ws_invalid_openapi_spec"),341 scopeId: ScopeIdSchema.make("ws_invalid_openapi_spec"),
336 sourceId: SourceIdSchema.make("src_invalid_openapi_spec"),342 sourceId: SourceIdSchema.make("src_invalid_openapi_spec"),
337 payload: {343 payload: {
338 name: "OpenAPI",344 name: "OpenAPI",
@@ -349,7 +355,7 @@ describe("source-definitions", () => { · 10 unmodified lines
349 355
350 const invalidOpenApiTransport = yield* Effect.flip(356 const invalidOpenApiTransport = yield* Effect.flip(
351 createSourceFromPayload({357 createSourceFromPayload({
352 workspaceId: WorkspaceIdSchema.make("ws_invalid_openapi_transport"),358 scopeId: ScopeIdSchema.make("ws_invalid_openapi_transport"),
353 sourceId: SourceIdSchema.make("src_invalid_openapi_transport"),359 sourceId: SourceIdSchema.make("src_invalid_openapi_transport"),
354 payload: {360 payload: {
355 name: "OpenAPI",361 name: "OpenAPI",
@@ -369,7 +375,7 @@ describe("source-definitions", () => { · 13 unmodified lines
369 375
370 const invalidGraphql = yield* Effect.flip(376 const invalidGraphql = yield* Effect.flip(
371 createSourceFromPayload({377 createSourceFromPayload({
372 workspaceId: WorkspaceIdSchema.make("ws_invalid_graphql"),378 scopeId: ScopeIdSchema.make("ws_invalid_graphql"),
373 sourceId: SourceIdSchema.make("src_invalid_graphql"),379 sourceId: SourceIdSchema.make("src_invalid_graphql"),
374 payload: {380 payload: {
375 name: "GraphQL",381 name: "GraphQL",
@@ -388,7 +394,7 @@ describe("source-definitions", () => { · 12 unmodified lines
388 394
389 const invalidInternal = yield* Effect.flip(395 const invalidInternal = yield* Effect.flip(
390 createSourceFromPayload({396 createSourceFromPayload({
391 workspaceId: WorkspaceIdSchema.make("ws_invalid_internal"),397 scopeId: ScopeIdSchema.make("ws_invalid_internal"),
392 sourceId: SourceIdSchema.make("src_invalid_internal"),398 sourceId: SourceIdSchema.make("src_invalid_internal"),
393 payload: {399 payload: {
394 name: "Internal",400 name: "Internal",
packages/platform/sdk/src/runtime/sources/source-definitions.ts+20−14
@@ -1,11 +1,13 @@
1import { sha256Hex } from "@executor/codemode-core";1import {
2 sha256Hex,
3} from "@executor/codemode-core";
2 4
3import type {5import type {
4 CreateSourcePayload,6 CreateSourcePayload,
5 UpdateSourcePayload,7 UpdateSourcePayload,
6} from "../../sources/contracts";8} from "../../sources/contracts";
7import type {9import type {
8 AccountId,10 ScopeId,
9 AuthArtifact,11 AuthArtifact,
10 Source,12 Source,
11 SourceAuth,13 SourceAuth,
@@ -17,7 +19,6 @@ import type { · 5 unmodified lines
17 StoredSourceRecord,19 StoredSourceRecord,
18 StoredSourceCatalogRecord,20 StoredSourceCatalogRecord,
19 StoredSourceCatalogRevisionRecord,21 StoredSourceCatalogRevisionRecord,
20 WorkspaceId,
21} from "#schema";22} from "#schema";
22import {23import {
23 AuthArtifactIdSchema,24 AuthArtifactIdSchema,
@@ -27,12 +28,17 @@ import { · 3 unmodified lines
27} from "#schema";28} from "#schema";
28import * as Effect from "effect/Effect";29import * as Effect from "effect/Effect";
29 30
30import { getSourceAdapter, getSourceAdapterForSource } from "./source-adapters";31import {
32 getSourceAdapter,
33 getSourceAdapterForSource,
34} from "./source-adapters";
31import {35import {
32 authArtifactFromSourceAuth,36 authArtifactFromSourceAuth,
33 sourceAuthFromAuthArtifact,37 sourceAuthFromAuthArtifact,
34} from "../auth/auth-artifacts";38} from "../auth/auth-artifacts";
35import { runtimeEffectError } from "../effect-errors";39import {
40 runtimeEffectError,
41} from "../effect-errors";
36 42
37const trimOrNull = (value: string | null | undefined): string | null => {43const trimOrNull = (value: string | null | undefined): string | null => {
38 if (value === null || value === undefined) {44 if (value === null || value === undefined) {
@@ -277,7 +283,7 @@ const validateSourceByKind = (source: Source): Effect.Effect<Source, Error, neve · 238 unmodified lines
277 );283 );
278 284
279export const createSourceFromPayload = (input: {285export const createSourceFromPayload = (input: {
280 workspaceId: WorkspaceId;286 scopeId: ScopeId;
281 sourceId: Source["id"];287 sourceId: Source["id"];
282 payload: CreateSourcePayload;288 payload: CreateSourcePayload;
283 now: number;289 now: number;
@@ -292,7 +298,7 @@ export const createSourceFromPayload = (input: { · 8 unmodified lines
292 298
293 return yield* validateSourceByKind({299 return yield* validateSourceByKind({
294 id: input.sourceId,300 id: input.sourceId,
295 workspaceId: input.workspaceId,301 scopeId: input.scopeId,
296 name: input.payload.name.trim(),302 name: input.payload.name.trim(),
297 kind: input.payload.kind,303 kind: input.payload.kind,
298 endpoint: input.payload.endpoint.trim(),304 endpoint: input.payload.endpoint.trim(),
@@ -370,7 +376,7 @@ export const createSourceCatalogRecord = (input: { · 71 unmodified lines
370 providerKey: sourceCatalogProviderKeyFromSource(input.source),376 providerKey: sourceCatalogProviderKeyFromSource(input.source),
371 name: input.source.name,377 name: input.source.name,
372 summary: null,378 summary: null,
373 visibility: "workspace",379 visibility: "scope",
374 latestRevisionId: input.latestRevisionId,380 latestRevisionId: input.latestRevisionId,
375 createdAt: input.source.createdAt,381 createdAt: input.source.createdAt,
376 updatedAt: input.source.updatedAt,382 updatedAt: input.source.updatedAt,
@@ -402,7 +408,7 @@ export const splitSourceForStorage = (input: { · 25 unmodified lines
402 source: Source;408 source: Source;
403 catalogId: SourceCatalogId;409 catalogId: SourceCatalogId;
404 catalogRevisionId: SourceCatalogRevisionId;410 catalogRevisionId: SourceCatalogRevisionId;
405 actorAccountId?: AccountId | null;411 actorScopeId?: ScopeId | null;
406 existingRuntimeAuthArtifactId?: AuthArtifact["id"] | null;412 existingRuntimeAuthArtifactId?: AuthArtifact["id"] | null;
407 existingImportAuthArtifactId?: AuthArtifact["id"] | null;413 existingImportAuthArtifactId?: AuthArtifact["id"] | null;
408}): {414}): {
@@ -412,7 +418,7 @@ export const splitSourceForStorage = (input: { · 3 unmodified lines
412} => {418} => {
413 const sourceRecord: StoredSourceRecord = {419 const sourceRecord: StoredSourceRecord = {
414 id: input.source.id,420 id: input.source.id,
415 workspaceId: input.source.workspaceId,421 scopeId: input.source.scopeId,
416 catalogId: input.catalogId,422 catalogId: input.catalogId,
417 catalogRevisionId: input.catalogRevisionId,423 catalogRevisionId: input.catalogRevisionId,
418 name: input.source.name,424 name: input.source.name,
@@ -435,7 +441,7 @@ export const splitSourceForStorage = (input: { · 16 unmodified lines
435 source: input.source,441 source: input.source,
436 auth: input.source.auth,442 auth: input.source.auth,
437 slot: "runtime",443 slot: "runtime",
438 actorAccountId: input.actorAccountId,444 actorScopeId: input.actorScopeId,
439 existingAuthArtifactId: input.existingRuntimeAuthArtifactId445 existingAuthArtifactId: input.existingRuntimeAuthArtifactId
440 ?? AuthArtifactIdSchema.make(`auth_art_${crypto.randomUUID()}`),446 ?? AuthArtifactIdSchema.make(`auth_art_${crypto.randomUUID()}`),
441 }),447 }),
@@ -444,7 +450,7 @@ export const splitSourceForStorage = (input: { · 2 unmodified lines
444 source: input.source,450 source: input.source,
445 auth: input.source.importAuth,451 auth: input.source.importAuth,
446 slot: "import",452 slot: "import",
447 actorAccountId: input.actorAccountId,453 actorScopeId: input.actorScopeId,
448 existingAuthArtifactId: input.existingImportAuthArtifactId454 existingAuthArtifactId: input.existingImportAuthArtifactId
449 ?? AuthArtifactIdSchema.make(`auth_art_${crypto.randomUUID()}`),455 ?? AuthArtifactIdSchema.make(`auth_art_${crypto.randomUUID()}`),
450 })456 })
@@ -466,7 +472,7 @@ export const projectSourceFromStorage = (input: { · 15 unmodified lines
466 472
467 return {473 return {
468 id: input.sourceRecord.id,474 id: input.sourceRecord.id,
469 workspaceId: input.sourceRecord.workspaceId,475 scopeId: input.sourceRecord.scopeId,
470 name: input.sourceRecord.name,476 name: input.sourceRecord.name,
471 kind: input.sourceRecord.kind,477 kind: input.sourceRecord.kind,
472 endpoint: input.sourceRecord.endpoint,478 endpoint: input.sourceRecord.endpoint,
@@ -507,7 +513,7 @@ export const projectSourcesFromStorage = (input: { · 34 unmodified lines
507 import: null,513 import: null,
508 };514 };
509 const current = authArtifact.slot === "runtime" ? existing.runtime : existing.import;515 const current = authArtifact.slot === "runtime" ? existing.runtime : existing.import;
510 if (current === null || (current.actorAccountId === null && authArtifact.actorAccountId !== null)) {516 if (current === null || (current.actorScopeId === null && authArtifact.actorScopeId !== null)) {
511 authArtifactsBySourceId.set(authArtifact.sourceId, {517 authArtifactsBySourceId.set(authArtifact.sourceId, {
512 ...existing,518 ...existing,
513 [authArtifact.slot]: authArtifact,519 [authArtifact.slot]: authArtifact,
packages/platform/sdk/src/runtime/sources/source-discovery.test.ts+8−2
@@ -4,10 +4,16 @@ import { · 3 unmodified lines
4 type ServerResponse,4 type ServerResponse,
5} from "node:http";5} from "node:http";
6 6
7import { describe, expect, it } from "@effect/vitest";7import {
8 describe,
9 expect,
10 it,
11} from "@effect/vitest";
8import * as Effect from "effect/Effect";12import * as Effect from "effect/Effect";
9 13
10import { discoverSource } from "./source-discovery";14import {
15 discoverSource,
16} from "./source-discovery";
11 17
12type TestServer = {18type TestServer = {
13 url: string;19 url: string;
packages/platform/sdk/src/runtime/sources/source-inspection.test.ts+11−3
@@ -1,8 +1,16 @@
1import { describe, expect, it } from "@effect/vitest";1import {
2 describe,
3 expect,
4 it,
5} from "@effect/vitest";
2import * as Effect from "effect/Effect";6import * as Effect from "effect/Effect";
3 7
4import type { LoadedSourceCatalogTool } from "../catalog/source/runtime";8import type {
5import { inspectionToolDetailFromTool } from "./source-inspection";9 LoadedSourceCatalogTool,
10} from "../catalog/source/runtime";
11import {
12 inspectionToolDetailFromTool,
13} from "./source-inspection";
6 14
7describe("source inspection", () => {15describe("source inspection", () => {
8 it.effect("keeps the shared TypeScript contract first-class on tool detail", () =>16 it.effect("keeps the shared TypeScript contract first-class on tool detail", () =>
packages/platform/sdk/src/runtime/sources/source-store.test.ts+20−10
@@ -1,17 +1,27 @@
1import { describe, expect, it } from "@effect/vitest";1import {
2 describe,
3 expect,
4 it,
5} from "@effect/vitest";
2import * as Effect from "effect/Effect";6import * as Effect from "effect/Effect";
3 7
4import {8import {
5 SourceIdSchema,9 SourceIdSchema,
6 WorkspaceIdSchema,10 ScopeIdSchema,
7 type Source,11 type Source,
8} from "#schema";12} from "#schema";
9 13
10import type { LoadedLocalExecutorConfig } from "../workspace-config";14import type {
11import { buildLocalSourceRecord } from "./source-store";15 LoadedLocalExecutorConfig,
12import type { LocalWorkspaceState } from "../workspace-state";16} from "../scope-config";
17import {
18 buildLocalSourceRecord,
19} from "./source-store";
20import type {
21 LocalScopeState,
22} from "../scope-state";
13 23
14const workspaceId = WorkspaceIdSchema.make("ws_source_store");24const scopeId = ScopeIdSchema.make("ws_source_store");
15const sourceId = SourceIdSchema.make("linear");25const sourceId = SourceIdSchema.make("linear");
16 26
17const loadedConfig: LoadedLocalExecutorConfig = {27const loadedConfig: LoadedLocalExecutorConfig = {
@@ -36,7 +46,7 @@ const loadedConfig: LoadedLocalExecutorConfig = { · 18 unmodified lines
36 projectConfigPath: "/tmp/project-config.jsonc",46 projectConfigPath: "/tmp/project-config.jsonc",
37};47};
38 48
39const workspaceState: LocalWorkspaceState = {49const scopeState: LocalScopeState = {
40 version: 1,50 version: 1,
41 sources: {51 sources: {
42 [sourceId]: {52 [sourceId]: {
@@ -54,16 +64,16 @@ describe("source-store", () => { · 11 unmodified lines
54 it.effect("projects configured sources without reading local artifacts", () =>64 it.effect("projects configured sources without reading local artifacts", () =>
55 Effect.gen(function* () {65 Effect.gen(function* () {
56 const result = yield* buildLocalSourceRecord({66 const result = yield* buildLocalSourceRecord({
57 workspaceId,67 scopeId,
58 loadedConfig,68 loadedConfig,
59 workspaceState,69 scopeState,
60 sourceId,70 sourceId,
61 authArtifacts: [],71 authArtifacts: [],
62 });72 });
63 73
64 expect(result.source).toEqual({74 expect(result.source).toEqual({
65 id: sourceId,75 id: sourceId,
66 workspaceId,76 scopeId,
67 name: "Linear GraphQL",77 name: "Linear GraphQL",
68 kind: "graphql",78 kind: "graphql",
69 endpoint: "https://api.linear.app/graphql",79 endpoint: "https://api.linear.app/graphql",
packages/platform/sdk/src/runtime/sources/source-store.ts+60−52
@@ -1,22 +1,30 @@
1import type {1import type {
2 AccountId,2 ScopeId,
3 Source,3 Source,
4 WorkspaceId,
5} from "#schema";4} from "#schema";
6import * as Context from "effect/Context";5import * as Context from "effect/Context";
7import * as Effect from "effect/Effect";6import * as Effect from "effect/Effect";
8import * as Layer from "effect/Layer";7import * as Layer from "effect/Layer";
9 8
10import { SourceTypeDeclarationsRefresherService } from "../catalog/source/type-declarations";9import {
10 SourceTypeDeclarationsRefresherService,
11} from "../catalog/source/type-declarations";
11import {12import {
12 SourceArtifactStore,13 SourceArtifactStore,
13 type WorkspaceStorageServices,14 type ScopeStorageServices,
14 WorkspaceConfigStore,15 ScopeConfigStore,
15 WorkspaceStateStore,16 ScopeStateStore,
16} from "../workspace/storage";17} from "../scope/storage";
17import { SecretMaterialDeleterService } from "../workspace/secret-material-providers";18import {
18import { RuntimeLocalWorkspaceService } from "../workspace/runtime-context";19 SecretMaterialDeleterService,
19import { ExecutorStateStore, type ExecutorStateStoreShape } from "../executor-state-store";20} from "../scope/secret-material-providers";
21import {
22 RuntimeLocalScopeService,
23} from "../scope/runtime-context";
24import {
25 ExecutorStateStore,
26 type ExecutorStateStoreShape,
27} from "../executor-state-store";
20import {28import {
21 loadRuntimeSourceStoreDeps,29 loadRuntimeSourceStoreDeps,
22 type RuntimeSourceStoreDeps,30 type RuntimeSourceStoreDeps,
@@ -35,95 +43,95 @@ import { · 12 unmodified lines
35export { buildLocalSourceRecord } from "./source-store/records";43export { buildLocalSourceRecord } from "./source-store/records";
36 44
37type RuntimeSourceStoreShape = {45type RuntimeSourceStoreShape = {
38 loadSourcesInWorkspace: (46 loadSourcesInScope: (
39 workspaceId: WorkspaceId,47 scopeId: ScopeId,
40 options?: { actorAccountId?: AccountId | null },48 options?: { actorScopeId?: ScopeId | null },
41 ) => ReturnType<typeof loadSourcesInWorkspaceWithDeps>;49 ) => ReturnType<typeof loadSourcesInWorkspaceWithDeps>;
42 listLinkedSecretSourcesInWorkspace: (50 listLinkedSecretSourcesInScope: (
43 workspaceId: WorkspaceId,51 scopeId: ScopeId,
44 options?: { actorAccountId?: AccountId | null },52 options?: { actorScopeId?: ScopeId | null },
45 ) => ReturnType<typeof listLinkedSecretSourcesInWorkspaceWithDeps>;53 ) => ReturnType<typeof listLinkedSecretSourcesInWorkspaceWithDeps>;
46 loadSourceById: (input: {54 loadSourceById: (input: {
47 workspaceId: WorkspaceId;55 scopeId: ScopeId;
48 sourceId: Source["id"];56 sourceId: Source["id"];
49 actorAccountId?: AccountId | null;57 actorScopeId?: ScopeId | null;
50 }) => ReturnType<typeof loadSourceByIdWithDeps>;58 }) => ReturnType<typeof loadSourceByIdWithDeps>;
51 removeSourceById: (input: {59 removeSourceById: (input: {
52 workspaceId: WorkspaceId;60 scopeId: ScopeId;
53 sourceId: Source["id"];61 sourceId: Source["id"];
54 }) => ReturnType<typeof removeSourceByIdWithDeps>;62 }) => ReturnType<typeof removeSourceByIdWithDeps>;
55 persistSource: (63 persistSource: (
56 source: Source,64 source: Source,
57 options?: { actorAccountId?: AccountId | null },65 options?: { actorScopeId?: ScopeId | null },
58 ) => ReturnType<typeof persistSourceWithDeps>;66 ) => ReturnType<typeof persistSourceWithDeps>;
59};67};
60 68
61export type RuntimeSourceStore = RuntimeSourceStoreShape;69export type RuntimeSourceStore = RuntimeSourceStoreShape;
62 70
63export const loadSourcesInWorkspace = (71export const loadSourcesInScope = (
64 executorState: ExecutorStateStoreShape,72 executorState: ExecutorStateStoreShape,
65 workspaceId: WorkspaceId,73 scopeId: ScopeId,
66 options: {74 options: {
67 actorAccountId?: AccountId | null;75 actorScopeId?: ScopeId | null;
68 } = {},76 } = {},
69): Effect.Effect<77): Effect.Effect<
70 readonly Source[],78 readonly Source[],
71 Error,79 Error,
72 WorkspaceStorageServices | SourceTypeDeclarationsRefresherService80 ScopeStorageServices | SourceTypeDeclarationsRefresherService
73> =>81> =>
74 Effect.flatMap(82 Effect.flatMap(
75 loadRuntimeSourceStoreDeps(executorState, workspaceId),83 loadRuntimeSourceStoreDeps(executorState, scopeId),
76 (deps) => loadSourcesInWorkspaceWithDeps(deps, workspaceId, options),84 (deps) => loadSourcesInWorkspaceWithDeps(deps, scopeId, options),
77 );85 );
78 86
79export const listLinkedSecretSourcesInWorkspace = (87export const listLinkedSecretSourcesInScope = (
80 executorState: ExecutorStateStoreShape,88 executorState: ExecutorStateStoreShape,
81 workspaceId: WorkspaceId,89 scopeId: ScopeId,
82 options: {90 options: {
83 actorAccountId?: AccountId | null;91 actorScopeId?: ScopeId | null;
84 } = {},92 } = {},
85): Effect.Effect<93): Effect.Effect<
86 Map<string, Array<{ sourceId: string; sourceName: string }>>,94 Map<string, Array<{ sourceId: string; sourceName: string }>>,
87 Error,95 Error,
88 WorkspaceStorageServices | SourceTypeDeclarationsRefresherService96 ScopeStorageServices | SourceTypeDeclarationsRefresherService
89> =>97> =>
90 Effect.flatMap(98 Effect.flatMap(
91 loadRuntimeSourceStoreDeps(executorState, workspaceId),99 loadRuntimeSourceStoreDeps(executorState, scopeId),
92 (deps) => listLinkedSecretSourcesInWorkspaceWithDeps(deps, workspaceId, options),100 (deps) => listLinkedSecretSourcesInWorkspaceWithDeps(deps, scopeId, options),
93 );101 );
94 102
95export const loadSourceById = (103export const loadSourceById = (
96 executorState: ExecutorStateStoreShape,104 executorState: ExecutorStateStoreShape,
97 input: {105 input: {
98 workspaceId: WorkspaceId;106 scopeId: ScopeId;
99 sourceId: Source["id"];107 sourceId: Source["id"];
100 actorAccountId?: AccountId | null;108 actorScopeId?: ScopeId | null;
101 },109 },
102): Effect.Effect<110): Effect.Effect<
103 Source,111 Source,
104 Error,112 Error,
105 WorkspaceStorageServices | SourceTypeDeclarationsRefresherService113 ScopeStorageServices | SourceTypeDeclarationsRefresherService
106> =>114> =>
107 Effect.flatMap(115 Effect.flatMap(
108 loadRuntimeSourceStoreDeps(executorState, input.workspaceId),116 loadRuntimeSourceStoreDeps(executorState, input.scopeId),
109 (deps) => loadSourceByIdWithDeps(deps, input),117 (deps) => loadSourceByIdWithDeps(deps, input),
110 );118 );
111 119
112export const removeSourceById = (120export const removeSourceById = (
113 executorState: ExecutorStateStoreShape,121 executorState: ExecutorStateStoreShape,
114 input: {122 input: {
115 workspaceId: WorkspaceId;123 scopeId: ScopeId;
116 sourceId: Source["id"];124 sourceId: Source["id"];
117 },125 },
118): Effect.Effect<126): Effect.Effect<
119 boolean,127 boolean,
120 Error,128 Error,
121 | WorkspaceStorageServices129 | ScopeStorageServices
122 | SourceTypeDeclarationsRefresherService130 | SourceTypeDeclarationsRefresherService
123 | SecretMaterialDeleterService131 | SecretMaterialDeleterService
124> =>132> =>
125 Effect.gen(function* () {133 Effect.gen(function* () {
126 const deps = yield* loadRuntimeSourceStoreDeps(executorState, input.workspaceId);134 const deps = yield* loadRuntimeSourceStoreDeps(executorState, input.scopeId);
127 const deleteSecretMaterial = yield* SecretMaterialDeleterService;135 const deleteSecretMaterial = yield* SecretMaterialDeleterService;
128 return yield* removeSourceByIdWithDeps(deps, input, deleteSecretMaterial);136 return yield* removeSourceByIdWithDeps(deps, input, deleteSecretMaterial);
129 });137 });
@@ -132,17 +140,17 @@ export const persistSource = ( · 2 unmodified lines
132 executorState: ExecutorStateStoreShape,140 executorState: ExecutorStateStoreShape,
133 source: Source,141 source: Source,
134 options: {142 options: {
135 actorAccountId?: AccountId | null;143 actorScopeId?: ScopeId | null;
136 } = {},144 } = {},
137): Effect.Effect<145): Effect.Effect<
138 Source,146 Source,
139 Error,147 Error,
140 | WorkspaceStorageServices148 | ScopeStorageServices
141 | SourceTypeDeclarationsRefresherService149 | SourceTypeDeclarationsRefresherService
142 | SecretMaterialDeleterService150 | SecretMaterialDeleterService
143> =>151> =>
144 Effect.gen(function* () {152 Effect.gen(function* () {
145 const deps = yield* loadRuntimeSourceStoreDeps(executorState, source.workspaceId);153 const deps = yield* loadRuntimeSourceStoreDeps(executorState, source.scopeId);
146 const deleteSecretMaterial = yield* SecretMaterialDeleterService;154 const deleteSecretMaterial = yield* SecretMaterialDeleterService;
147 return yield* persistSourceWithDeps(deps, source, options, deleteSecretMaterial);155 return yield* persistSourceWithDeps(deps, source, options, deleteSecretMaterial);
148 });156 });
@@ -155,9 +163,9 @@ export const RuntimeSourceStoreLive = Layer.effect( · 6 unmodified lines
155 RuntimeSourceStoreService,163 RuntimeSourceStoreService,
156 Effect.gen(function* () {164 Effect.gen(function* () {
157 const executorState = yield* ExecutorStateStore;165 const executorState = yield* ExecutorStateStore;
158 const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService;166 const runtimeLocalScope = yield* RuntimeLocalScopeService;
159 const workspaceConfigStore = yield* WorkspaceConfigStore;167 const scopeConfigStore = yield* ScopeConfigStore;
160 const workspaceStateStore = yield* WorkspaceStateStore;168 const scopeStateStore = yield* ScopeStateStore;
161 const sourceArtifactStore = yield* SourceArtifactStore;169 const sourceArtifactStore = yield* SourceArtifactStore;
162 const sourceTypeDeclarationsRefresher =170 const sourceTypeDeclarationsRefresher =
163 yield* SourceTypeDeclarationsRefresherService;171 yield* SourceTypeDeclarationsRefresherService;
@@ -165,18 +173,18 @@ export const RuntimeSourceStoreLive = Layer.effect( · 1 unmodified line
165 173
166 const deps: RuntimeSourceStoreDeps = {174 const deps: RuntimeSourceStoreDeps = {
167 executorState,175 executorState,
168 runtimeLocalWorkspace,176 runtimeLocalScope,
169 workspaceConfigStore,177 scopeConfigStore,
170 workspaceStateStore,178 scopeStateStore,
171 sourceArtifactStore,179 sourceArtifactStore,
172 sourceTypeDeclarationsRefresher,180 sourceTypeDeclarationsRefresher,
173 };181 };
174 182
175 return RuntimeSourceStoreService.of({183 return RuntimeSourceStoreService.of({
176 loadSourcesInWorkspace: (workspaceId, options = {}) =>184 loadSourcesInScope: (scopeId, options = {}) =>
177 loadSourcesInWorkspaceWithDeps(deps, workspaceId, options),185 loadSourcesInWorkspaceWithDeps(deps, scopeId, options),
178 listLinkedSecretSourcesInWorkspace: (workspaceId, options = {}) =>186 listLinkedSecretSourcesInScope: (scopeId, options = {}) =>
179 listLinkedSecretSourcesInWorkspaceWithDeps(deps, workspaceId, options),187 listLinkedSecretSourcesInWorkspaceWithDeps(deps, scopeId, options),
180 loadSourceById: (input) =>188 loadSourceById: (input) =>
181 loadSourceByIdWithDeps(deps, input),189 loadSourceByIdWithDeps(deps, input),
182 removeSourceById: (input) =>190 removeSourceById: (input) =>
packages/platform/sdk/src/runtime/sources/source-store/auth.ts+27−18
@@ -1,18 +1,27 @@
1import type {1import type {
2 AccountId,2 ScopeId,
3 AuthArtifact,3 AuthArtifact,
4 CredentialSlot,4 CredentialSlot,
5 ProviderAuthGrant,5 ProviderAuthGrant,
6 Source,6 Source,
7 WorkspaceId,
8} from "#schema";7} from "#schema";
9import { decodeProviderGrantRefAuthArtifactConfig } from "#schema";8import {
9 decodeProviderGrantRefAuthArtifactConfig,
10} from "#schema";
10import * as Effect from "effect/Effect";11import * as Effect from "effect/Effect";
11 12
12import { authArtifactSecretMaterialRefs } from "../../auth/auth-artifacts";13import {
13import { removeAuthLeaseAndSecrets } from "../../auth/auth-leases";14 authArtifactSecretMaterialRefs,
14import type { DeleteSecretMaterial } from "../../workspace/secret-material-providers";15} from "../../auth/auth-artifacts";
15import type { ExecutorStateStoreShape } from "../../executor-state-store";16import {
17 removeAuthLeaseAndSecrets,
18} from "../../auth/auth-leases";
19import type {
20 DeleteSecretMaterial,
21} from "../../scope/secret-material-providers";
22import type {
23 ExecutorStateStoreShape,
24} from "../../executor-state-store";
16 25
17const secretRefKey = (ref: { providerId: string; handle: string }): string =>26const secretRefKey = (ref: { providerId: string; handle: string }): string =>
18 `${ref.providerId}:${ref.handle}`;27 `${ref.providerId}:${ref.handle}`;
@@ -60,52 +69,52 @@ export const providerGrantIdsFromArtifacts = ( · 41 unmodified lines
60 69
61export const selectPreferredAuthArtifact = (input: {70export const selectPreferredAuthArtifact = (input: {
62 authArtifacts: ReadonlyArray<AuthArtifact>;71 authArtifacts: ReadonlyArray<AuthArtifact>;
63 actorAccountId?: AccountId | null;72 actorScopeId?: ScopeId | null;
64 slot: CredentialSlot;73 slot: CredentialSlot;
65}): AuthArtifact | null => {74}): AuthArtifact | null => {
66 const matchingSlot = input.authArtifacts.filter(75 const matchingSlot = input.authArtifacts.filter(
67 (artifact) => artifact.slot === input.slot,76 (artifact) => artifact.slot === input.slot,
68 );77 );
69 78
70 if (input.actorAccountId !== undefined) {79 if (input.actorScopeId !== undefined) {
71 const exact = matchingSlot.find(80 const exact = matchingSlot.find(
72 (artifact) => artifact.actorAccountId === input.actorAccountId,81 (artifact) => artifact.actorScopeId === input.actorScopeId,
73 );82 );
74 if (exact) {83 if (exact) {
75 return exact;84 return exact;
76 }85 }
77 }86 }
78 87
79 return matchingSlot.find((artifact) => artifact.actorAccountId === null) ?? null;88 return matchingSlot.find((artifact) => artifact.actorScopeId === null) ?? null;
80};89};
81 90
82export const selectExactAuthArtifact = (input: {91export const selectExactAuthArtifact = (input: {
83 authArtifacts: ReadonlyArray<AuthArtifact>;92 authArtifacts: ReadonlyArray<AuthArtifact>;
84 actorAccountId?: AccountId | null;93 actorScopeId?: ScopeId | null;
85 slot: CredentialSlot;94 slot: CredentialSlot;
86}): AuthArtifact | null =>95}): AuthArtifact | null =>
87 input.authArtifacts.find(96 input.authArtifacts.find(
88 (artifact) =>97 (artifact) =>
89 artifact.slot === input.slot &&98 artifact.slot === input.slot &&
90 artifact.actorAccountId === (input.actorAccountId ?? null),99 artifact.actorScopeId === (input.actorScopeId ?? null),
91 ) ?? null;100 ) ?? null;
92 101
93export const removeAuthArtifactsForSource = (102export const removeAuthArtifactsForSource = (
94 executorState: ExecutorStateStoreShape,103 executorState: ExecutorStateStoreShape,
95 input: {104 input: {
96 workspaceId: WorkspaceId;105 scopeId: ScopeId;
97 sourceId: Source["id"];106 sourceId: Source["id"];
98 },107 },
99 deleteSecretMaterial: DeleteSecretMaterial,108 deleteSecretMaterial: DeleteSecretMaterial,
100) =>109) =>
101 Effect.gen(function* () {110 Effect.gen(function* () {
102 const existingAuthArtifacts = yield* executorState.authArtifacts.listByWorkspaceAndSourceId({111 const existingAuthArtifacts = yield* executorState.authArtifacts.listByScopeAndSourceId({
103 workspaceId: input.workspaceId,112 scopeId: input.scopeId,
104 sourceId: input.sourceId,113 sourceId: input.sourceId,
105 });114 });
106 115
107 yield* executorState.authArtifacts.removeByWorkspaceAndSourceId({116 yield* executorState.authArtifacts.removeByScopeAndSourceId({
108 workspaceId: input.workspaceId,117 scopeId: input.scopeId,
109 sourceId: input.sourceId,118 sourceId: input.sourceId,
110 });119 });
111 120
packages/platform/sdk/src/runtime/sources/source-store/config.ts+16−6
@@ -4,16 +4,26 @@ import type { · 3 unmodified lines
4 Source,4 Source,
5 SourceId,5 SourceId,
6} from "#schema";6} from "#schema";
7import { SourceIdSchema } from "#schema";7import {
8 SourceIdSchema,
9} from "#schema";
8 10
9import type { LoadedLocalExecutorConfig } from "../../workspace-config";11import type {
10import { LocalUnsupportedSourceKindError } from "../../workspace-errors";12 LoadedLocalExecutorConfig,
13} from "../../scope-config";
14import {
15 LocalUnsupportedSourceKindError,
16} from "../../scope-errors";
11import {17import {
12 fromConfigSecretProviderId,18 fromConfigSecretProviderId,
13 toConfigSecretProviderId,19 toConfigSecretProviderId,
14} from "../../workspace/config-secrets";20} from "../../scope/config-secrets";
15import { getSourceAdapterForSource } from "../source-adapters";21import {
16import { slugify } from "../slug";22 getSourceAdapterForSource,
23} from "../source-adapters";
24import {
25 slugify,
26} from "../slug";
17 27
18export const trimOrNull = (value: string | null | undefined): string | null => {28export const trimOrNull = (value: string | null | undefined): string | null => {
19 if (value == null) {29 if (value == null) {
packages/platform/sdk/src/runtime/sources/source-store/deps.ts+60−52
@@ -1,112 +1,120 @@
1import type { AccountId, WorkspaceId } from "#schema";1import type {
2 ScopeId,
3} from "#schema";
2import * as Effect from "effect/Effect";4import * as Effect from "effect/Effect";
3 5
4import type { LoadedLocalExecutorConfig } from "../../workspace-config";6import type {
7 LoadedLocalExecutorConfig,
8} from "../../scope-config";
5import {9import {
6 SourceTypeDeclarationsRefresherService,10 SourceTypeDeclarationsRefresherService,
7 type SourceTypeDeclarationsRefresherShape,11 type SourceTypeDeclarationsRefresherShape,
8} from "../../catalog/source/type-declarations";12} from "../../catalog/source/type-declarations";
9import {13import {
10 RuntimeLocalWorkspaceMismatchError,14 RuntimeLocalScopeMismatchError,
11 RuntimeLocalWorkspaceUnavailableError,15 RuntimeLocalScopeUnavailableError,
12} from "../../workspace-errors";16} from "../../scope-errors";
13import {17import {
14 requireRuntimeLocalWorkspace,18 requireRuntimeLocalScope,
15 type RuntimeLocalWorkspaceState,19 type RuntimeLocalScopeState,
16} from "../../workspace/runtime-context";20} from "../../scope/runtime-context";
17import type {21import type {
18 SourceArtifactStoreShape,22 SourceArtifactStoreShape,
19 WorkspaceStorageServices,23 ScopeStorageServices,
20 WorkspaceConfigStoreShape,24 ScopeConfigStoreShape,
21 WorkspaceStateStoreShape,25 ScopeStateStoreShape,
22} from "../../workspace/storage";26} from "../../scope/storage";
23import {27import {
24 SourceArtifactStore,28 SourceArtifactStore,
25 WorkspaceConfigStore,29 ScopeConfigStore,
26 WorkspaceStateStore,30 ScopeStateStore,
27} from "../../workspace/storage";31} from "../../scope/storage";
28import type { LocalWorkspaceState } from "../../workspace-state";32import type {
29import type { ExecutorStateStoreShape } from "../../executor-state-store";33 LocalScopeState,
34} from "../../scope-state";
35import type {
36 ExecutorStateStoreShape,
37} from "../../executor-state-store";
30 38
31export type RuntimeSourceStoreDeps = {39export type RuntimeSourceStoreDeps = {
32 executorState: ExecutorStateStoreShape;40 executorState: ExecutorStateStoreShape;
33 runtimeLocalWorkspace: RuntimeLocalWorkspaceState;41 runtimeLocalScope: RuntimeLocalScopeState;
34 workspaceConfigStore: WorkspaceConfigStoreShape;42 scopeConfigStore: ScopeConfigStoreShape;
35 workspaceStateStore: WorkspaceStateStoreShape;43 scopeStateStore: ScopeStateStoreShape;
36 sourceArtifactStore: SourceArtifactStoreShape;44 sourceArtifactStore: SourceArtifactStoreShape;
37 sourceTypeDeclarationsRefresher: SourceTypeDeclarationsRefresherShape;45 sourceTypeDeclarationsRefresher: SourceTypeDeclarationsRefresherShape;
38};46};
39 47
40export type ResolvedSourceStoreWorkspace = {48export type ResolvedSourceStoreScope = {
41 installation: {49 installation: {
42 workspaceId: WorkspaceId;50 scopeId: ScopeId;
43 accountId: AccountId;51 actorScopeId: ScopeId;
44 };52 };
45 workspaceConfigStore: WorkspaceConfigStoreShape;53 scopeConfigStore: ScopeConfigStoreShape;
46 workspaceStateStore: WorkspaceStateStoreShape;54 scopeStateStore: ScopeStateStoreShape;
47 sourceArtifactStore: SourceArtifactStoreShape;55 sourceArtifactStore: SourceArtifactStoreShape;
48 loadedConfig: LoadedLocalExecutorConfig;56 loadedConfig: LoadedLocalExecutorConfig;
49 workspaceState: LocalWorkspaceState;57 scopeState: LocalScopeState;
50};58};
51 59
52export type RuntimeSourceStoreServices =60export type RuntimeSourceStoreServices =
53 WorkspaceStorageServices | SourceTypeDeclarationsRefresherService;61 ScopeStorageServices | SourceTypeDeclarationsRefresherService;
54 62
55export const resolveRuntimeLocalWorkspaceFromDeps = (63export const resolveRuntimeLocalScopeFromDeps = (
56 deps: RuntimeSourceStoreDeps,64 deps: RuntimeSourceStoreDeps,
57 workspaceId: WorkspaceId,65 scopeId: ScopeId,
58): Effect.Effect<66): Effect.Effect<
59 ResolvedSourceStoreWorkspace,67 ResolvedSourceStoreScope,
60 | RuntimeLocalWorkspaceUnavailableError68 | RuntimeLocalScopeUnavailableError
61 | RuntimeLocalWorkspaceMismatchError69 | RuntimeLocalScopeMismatchError
62 | Error,70 | Error,
63 never71 never
64> =>72> =>
65 Effect.gen(function* () {73 Effect.gen(function* () {
66 if (deps.runtimeLocalWorkspace.installation.workspaceId !== workspaceId) {74 if (deps.runtimeLocalScope.installation.scopeId !== scopeId) {
67 return yield* new RuntimeLocalWorkspaceMismatchError({75 return yield* new RuntimeLocalScopeMismatchError({
68 message: `Runtime local workspace mismatch: expected ${workspaceId}, got ${deps.runtimeLocalWorkspace.installation.workspaceId}`,76 message: `Runtime local scope mismatch: expected ${scopeId}, got ${deps.runtimeLocalScope.installation.scopeId}`,
69 requestedWorkspaceId: workspaceId,77 requestedScopeId: scopeId,
70 activeWorkspaceId: deps.runtimeLocalWorkspace.installation.workspaceId,78 activeScopeId: deps.runtimeLocalScope.installation.scopeId,
71 });79 });
72 }80 }
73 81
74 const loadedConfig = yield* deps.workspaceConfigStore.load();82 const loadedConfig = yield* deps.scopeConfigStore.load();
75 const workspaceState = yield* deps.workspaceStateStore.load();83 const scopeState = yield* deps.scopeStateStore.load();
76 84
77 return {85 return {
78 installation: deps.runtimeLocalWorkspace.installation,86 installation: deps.runtimeLocalScope.installation,
79 workspaceConfigStore: deps.workspaceConfigStore,87 scopeConfigStore: deps.scopeConfigStore,
80 workspaceStateStore: deps.workspaceStateStore,88 scopeStateStore: deps.scopeStateStore,
81 sourceArtifactStore: deps.sourceArtifactStore,89 sourceArtifactStore: deps.sourceArtifactStore,
82 loadedConfig,90 loadedConfig,
83 workspaceState,91 scopeState,
84 };92 };
85 });93 });
86 94
87export const loadRuntimeSourceStoreDeps = (95export const loadRuntimeSourceStoreDeps = (
88 executorState: ExecutorStateStoreShape,96 executorState: ExecutorStateStoreShape,
89 workspaceId: WorkspaceId,97 scopeId: ScopeId,
90): Effect.Effect<98): Effect.Effect<
91 RuntimeSourceStoreDeps,99 RuntimeSourceStoreDeps,
92 | RuntimeLocalWorkspaceUnavailableError100 | RuntimeLocalScopeUnavailableError
93 | RuntimeLocalWorkspaceMismatchError101 | RuntimeLocalScopeMismatchError
94 | Error,102 | Error,
95 RuntimeSourceStoreServices103 RuntimeSourceStoreServices
96> =>104> =>
97 Effect.gen(function* () {105 Effect.gen(function* () {
98 const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(workspaceId);106 const runtimeLocalScope = yield* requireRuntimeLocalScope(scopeId);
99 const workspaceConfigStore = yield* WorkspaceConfigStore;107 const scopeConfigStore = yield* ScopeConfigStore;
100 const workspaceStateStore = yield* WorkspaceStateStore;108 const scopeStateStore = yield* ScopeStateStore;
101 const sourceArtifactStore = yield* SourceArtifactStore;109 const sourceArtifactStore = yield* SourceArtifactStore;
102 const sourceTypeDeclarationsRefresher =110 const sourceTypeDeclarationsRefresher =
103 yield* SourceTypeDeclarationsRefresherService;111 yield* SourceTypeDeclarationsRefresherService;
104 112
105 return {113 return {
106 executorState,114 executorState,
107 runtimeLocalWorkspace,115 runtimeLocalScope,
108 workspaceConfigStore,116 scopeConfigStore,
109 workspaceStateStore,117 scopeStateStore,
110 sourceArtifactStore,118 sourceArtifactStore,
111 sourceTypeDeclarationsRefresher,119 sourceTypeDeclarationsRefresher,
112 };120 };
packages/platform/sdk/src/runtime/sources/source-store/lifecycle.ts+69−60
@@ -1,13 +1,22 @@
1import type { AccountId, Source, WorkspaceId } from "#schema";1import type {
2 ScopeId,
3 Source,
4} from "#schema";
2import * as Effect from "effect/Effect";5import * as Effect from "effect/Effect";
3 6
4import { removeAuthLeaseAndSecrets } from "../../auth/auth-leases";7import {
8 removeAuthLeaseAndSecrets,
9} from "../../auth/auth-leases";
5import {10import {
6 clearProviderGrantOrphanedAt,11 clearProviderGrantOrphanedAt,
7 markProviderGrantOrphanedIfUnused,12 markProviderGrantOrphanedIfUnused,
8} from "../../auth/provider-grant-lifecycle";13} from "../../auth/provider-grant-lifecycle";
9import type { DeleteSecretMaterial } from "../../workspace/secret-material-providers";14import type {
10import type { LocalWorkspaceState } from "../../workspace-state";15 DeleteSecretMaterial,
16} from "../../scope/secret-material-providers";
17import type {
18 LocalScopeState,
19} from "../../scope-state";
11import {20import {
12 stableSourceCatalogId,21 stableSourceCatalogId,
13 stableSourceCatalogRevisionId,22 stableSourceCatalogRevisionId,
@@ -26,37 +35,37 @@ import { · 12 unmodified lines
26} from "./config";35} from "./config";
27import {36import {
28 type RuntimeSourceStoreDeps,37 type RuntimeSourceStoreDeps,
29 resolveRuntimeLocalWorkspaceFromDeps,38 resolveRuntimeLocalScopeFromDeps,
30} from "./deps";39} from "./deps";
31import {40import {
32 loadSourceByIdWithDeps,41 loadSourceByIdWithDeps,
33 shouldRefreshWorkspaceDeclarationsAfterPersist,42 shouldRefreshScopeDeclarationsAfterPersist,
34 syncWorkspaceSourceTypeDeclarationsWithDeps,43 syncScopeSourceTypeDeclarationsWithDeps,
35} from "./records";44} from "./records";
36 45
37export const removeSourceByIdWithDeps = (46export const removeSourceByIdWithDeps = (
38 deps: RuntimeSourceStoreDeps,47 deps: RuntimeSourceStoreDeps,
39 input: {48 input: {
40 workspaceId: WorkspaceId;49 scopeId: ScopeId;
41 sourceId: Source["id"];50 sourceId: Source["id"];
42 },51 },
43 deleteSecretMaterial: DeleteSecretMaterial,52 deleteSecretMaterial: DeleteSecretMaterial,
44): Effect.Effect<boolean, Error, never> =>53): Effect.Effect<boolean, Error, never> =>
45 Effect.gen(function* () {54 Effect.gen(function* () {
46 const localWorkspace = yield* resolveRuntimeLocalWorkspaceFromDeps(55 const localScope = yield* resolveRuntimeLocalScopeFromDeps(
47 deps,56 deps,
48 input.workspaceId,57 input.scopeId,
49 );58 );
50 if (!localWorkspace.loadedConfig.config?.sources?.[input.sourceId]) {59 if (!localScope.loadedConfig.config?.sources?.[input.sourceId]) {
51 return false;60 return false;
52 }61 }
53 62
54 const projectConfig = cloneJson(localWorkspace.loadedConfig.projectConfig ?? {});63 const projectConfig = cloneJson(localScope.loadedConfig.projectConfig ?? {});
55 const sources = {64 const sources = {
56 ...projectConfig.sources,65 ...projectConfig.sources,
57 };66 };
58 delete sources[input.sourceId];67 delete sources[input.sourceId];
59 yield* localWorkspace.workspaceConfigStore.writeProject({68 yield* localScope.scopeConfigStore.writeProject({
60 config: {69 config: {
61 ...projectConfig,70 ...projectConfig,
62 sources,71 sources,
@@ -64,30 +73,30 @@ export const removeSourceByIdWithDeps = ( · 1 unmodified line
64 });73 });
65 74
66 const { [input.sourceId]: _removedSource, ...remainingSources } =75 const { [input.sourceId]: _removedSource, ...remainingSources } =
67 localWorkspace.workspaceState.sources;76 localScope.scopeState.sources;
68 const workspaceState: LocalWorkspaceState = {77 const scopeState: LocalScopeState = {
69 ...localWorkspace.workspaceState,78 ...localScope.scopeState,
70 sources: remainingSources,79 sources: remainingSources,
71 };80 };
72 yield* localWorkspace.workspaceStateStore.write({81 yield* localScope.scopeStateStore.write({
73 state: workspaceState,82 state: scopeState,
74 });83 });
75 yield* localWorkspace.sourceArtifactStore.remove({84 yield* localScope.sourceArtifactStore.remove({
76 sourceId: input.sourceId,85 sourceId: input.sourceId,
77 });86 });
78 const existingAuthArtifacts =87 const existingAuthArtifacts =
79 yield* deps.executorState.authArtifacts.listByWorkspaceAndSourceId({88 yield* deps.executorState.authArtifacts.listByScopeAndSourceId({
80 workspaceId: input.workspaceId,89 scopeId: input.scopeId,
81 sourceId: input.sourceId,90 sourceId: input.sourceId,
82 });91 });
83 const removedGrantIds = providerGrantIdsFromArtifacts(existingAuthArtifacts);92 const removedGrantIds = providerGrantIdsFromArtifacts(existingAuthArtifacts);
84 93
85 yield* deps.executorState.sourceAuthSessions.removeByWorkspaceAndSourceId(94 yield* deps.executorState.sourceAuthSessions.removeByScopeAndSourceId(
86 input.workspaceId,95 input.scopeId,
87 input.sourceId,96 input.sourceId,
88 );97 );
89 yield* deps.executorState.sourceOauthClients.removeByWorkspaceAndSourceId({98 yield* deps.executorState.sourceOauthClients.removeByScopeAndSourceId({
90 workspaceId: input.workspaceId,99 scopeId: input.scopeId,
91 sourceId: input.sourceId,100 sourceId: input.sourceId,
92 });101 });
93 yield* removeAuthArtifactsForSource(deps.executorState, input, deleteSecretMaterial);102 yield* removeAuthArtifactsForSource(deps.executorState, input, deleteSecretMaterial);
@@ -95,12 +104,12 @@ export const removeSourceByIdWithDeps = ( · 1 unmodified line
95 [...removedGrantIds],104 [...removedGrantIds],
96 (grantId) =>105 (grantId) =>
97 markProviderGrantOrphanedIfUnused(deps.executorState, {106 markProviderGrantOrphanedIfUnused(deps.executorState, {
98 workspaceId: input.workspaceId,107 scopeId: input.scopeId,
99 grantId,108 grantId,
100 }),109 }),
101 { discard: true },110 { discard: true },
102 );111 );
103 yield* syncWorkspaceSourceTypeDeclarationsWithDeps(deps, input.workspaceId);112 yield* syncScopeSourceTypeDeclarationsWithDeps(deps, input.scopeId);
104 113
105 return true;114 return true;
106 });115 });
@@ -109,42 +118,42 @@ export const persistSourceWithDeps = ( · 2 unmodified lines
109 deps: RuntimeSourceStoreDeps,118 deps: RuntimeSourceStoreDeps,
110 source: Source,119 source: Source,
111 options: {120 options: {
112 actorAccountId?: AccountId | null;121 actorScopeId?: ScopeId | null;
113 } = {},122 } = {},
114 deleteSecretMaterial: DeleteSecretMaterial,123 deleteSecretMaterial: DeleteSecretMaterial,
115): Effect.Effect<Source, Error, never> =>124): Effect.Effect<Source, Error, never> =>
116 Effect.gen(function* () {125 Effect.gen(function* () {
117 const localWorkspace = yield* resolveRuntimeLocalWorkspaceFromDeps(126 const localScope = yield* resolveRuntimeLocalScopeFromDeps(
118 deps,127 deps,
119 source.workspaceId,128 source.scopeId,
120 );129 );
121 const nextSource = {130 const nextSource = {
122 ...source,131 ...source,
123 id:132 id:
124 localWorkspace.loadedConfig.config?.sources?.[source.id] ||133 localScope.loadedConfig.config?.sources?.[source.id] ||
125 localWorkspace.workspaceState.sources[source.id]134 localScope.scopeState.sources[source.id]
126 ? source.id135 ? source.id
127 : deriveLocalSourceId(136 : deriveLocalSourceId(
128 source,137 source,
129 new Set(Object.keys(localWorkspace.loadedConfig.config?.sources ?? {})),138 new Set(Object.keys(localScope.loadedConfig.config?.sources ?? {})),
130 ),139 ),
131 } satisfies Source;140 } satisfies Source;
132 const existingAuthArtifacts =141 const existingAuthArtifacts =
133 yield* deps.executorState.authArtifacts.listByWorkspaceAndSourceId({142 yield* deps.executorState.authArtifacts.listByScopeAndSourceId({
134 workspaceId: nextSource.workspaceId,143 scopeId: nextSource.scopeId,
135 sourceId: nextSource.id,144 sourceId: nextSource.id,
136 });145 });
137 const existingRuntimeAuthArtifact = selectExactAuthArtifact({146 const existingRuntimeAuthArtifact = selectExactAuthArtifact({
138 authArtifacts: existingAuthArtifacts,147 authArtifacts: existingAuthArtifacts,
139 actorAccountId: options.actorAccountId,148 actorScopeId: options.actorScopeId,
140 slot: "runtime",149 slot: "runtime",
141 });150 });
142 const existingImportAuthArtifact = selectExactAuthArtifact({151 const existingImportAuthArtifact = selectExactAuthArtifact({
143 authArtifacts: existingAuthArtifacts,152 authArtifacts: existingAuthArtifacts,
144 actorAccountId: options.actorAccountId,153 actorScopeId: options.actorScopeId,
145 slot: "import",154 slot: "import",
146 });155 });
147 const projectConfig = cloneJson(localWorkspace.loadedConfig.projectConfig ?? {});156 const projectConfig = cloneJson(localScope.loadedConfig.projectConfig ?? {});
148 const sources = {157 const sources = {
149 ...projectConfig.sources,158 ...projectConfig.sources,
150 };159 };
@@ -152,9 +161,9 @@ export const persistSourceWithDeps = ( · 1 unmodified line
152 sources[nextSource.id] = configSourceFromLocalSource({161 sources[nextSource.id] = configSourceFromLocalSource({
153 source: nextSource,162 source: nextSource,
154 existingConfigAuth: existingConfigSource?.connection.auth,163 existingConfigAuth: existingConfigSource?.connection.auth,
155 config: localWorkspace.loadedConfig.config,164 config: localScope.loadedConfig.config,
156 });165 });
157 yield* localWorkspace.workspaceConfigStore.writeProject({166 yield* localScope.scopeConfigStore.writeProject({
158 config: {167 config: {
159 ...projectConfig,168 ...projectConfig,
160 sources,169 sources,
@@ -165,7 +174,7 @@ export const persistSourceWithDeps = ( · 4 unmodified lines
165 source: nextSource,174 source: nextSource,
166 catalogId: stableSourceCatalogId(nextSource),175 catalogId: stableSourceCatalogId(nextSource),
167 catalogRevisionId: stableSourceCatalogRevisionId(nextSource),176 catalogRevisionId: stableSourceCatalogRevisionId(nextSource),
168 actorAccountId: options.actorAccountId,177 actorScopeId: options.actorScopeId,
169 existingRuntimeAuthArtifactId: existingRuntimeAuthArtifact?.id ?? null,178 existingRuntimeAuthArtifactId: existingRuntimeAuthArtifact?.id ?? null,
170 existingImportAuthArtifactId: existingImportAuthArtifact?.id ?? null,179 existingImportAuthArtifactId: existingImportAuthArtifact?.id ?? null,
171 });180 });
@@ -176,10 +185,10 @@ export const persistSourceWithDeps = ( · 4 unmodified lines
176 authArtifactId: existingRuntimeAuthArtifact.id,185 authArtifactId: existingRuntimeAuthArtifact.id,
177 }, deleteSecretMaterial);186 }, deleteSecretMaterial);
178 }187 }
179 yield* deps.executorState.authArtifacts.removeByWorkspaceSourceAndActor({188 yield* deps.executorState.authArtifacts.removeByScopeSourceAndActor({
180 workspaceId: nextSource.workspaceId,189 scopeId: nextSource.scopeId,
181 sourceId: nextSource.id,190 sourceId: nextSource.id,
182 actorAccountId: options.actorAccountId ?? null,191 actorScopeId: options.actorScopeId ?? null,
183 slot: "runtime",192 slot: "runtime",
184 });193 });
185 } else {194 } else {
@@ -205,10 +214,10 @@ export const persistSourceWithDeps = ( · 19 unmodified lines
205 authArtifactId: existingImportAuthArtifact.id,214 authArtifactId: existingImportAuthArtifact.id,
206 }, deleteSecretMaterial);215 }, deleteSecretMaterial);
207 }216 }
208 yield* deps.executorState.authArtifacts.removeByWorkspaceSourceAndActor({217 yield* deps.executorState.authArtifacts.removeByScopeSourceAndActor({
209 workspaceId: nextSource.workspaceId,218 scopeId: nextSource.scopeId,
210 sourceId: nextSource.id,219 sourceId: nextSource.id,
211 actorAccountId: options.actorAccountId ?? null,220 actorScopeId: options.actorScopeId ?? null,
212 slot: "import",221 slot: "import",
213 });222 });
214 } else {223 } else {
@@ -249,17 +258,17 @@ export const persistSourceWithDeps = ( · 34 unmodified lines
249 [...previousGrantIds].filter((grantId) => !nextGrantIds.has(grantId)),258 [...previousGrantIds].filter((grantId) => !nextGrantIds.has(grantId)),
250 (grantId) =>259 (grantId) =>
251 markProviderGrantOrphanedIfUnused(deps.executorState, {260 markProviderGrantOrphanedIfUnused(deps.executorState, {
252 workspaceId: nextSource.workspaceId,261 scopeId: nextSource.scopeId,
253 grantId,262 grantId,
254 }),263 }),
255 { discard: true },264 { discard: true },
256 );265 );
257 266
258 const existingSourceState = localWorkspace.workspaceState.sources[nextSource.id];267 const existingSourceState = localScope.scopeState.sources[nextSource.id];
259 const workspaceState: LocalWorkspaceState = {268 const scopeState: LocalScopeState = {
260 ...localWorkspace.workspaceState,269 ...localScope.scopeState,
261 sources: {270 sources: {
262 ...localWorkspace.workspaceState.sources,271 ...localScope.scopeState.sources,
263 [nextSource.id]: {272 [nextSource.id]: {
264 status: nextSource.status,273 status: nextSource.status,
265 lastError: nextSource.lastError,274 lastError: nextSource.lastError,
@@ -269,27 +278,27 @@ export const persistSourceWithDeps = ( · 3 unmodified lines
269 },278 },
270 },279 },
271 };280 };
272 yield* localWorkspace.workspaceStateStore.write({281 yield* localScope.scopeStateStore.write({
273 state: workspaceState,282 state: scopeState,
274 });283 });
275 284
276 if (shouldRefreshWorkspaceDeclarationsAfterPersist(nextSource)) {285 if (shouldRefreshScopeDeclarationsAfterPersist(nextSource)) {
277 yield* syncWorkspaceSourceTypeDeclarationsWithDeps(286 yield* syncScopeSourceTypeDeclarationsWithDeps(
278 deps,287 deps,
279 nextSource.workspaceId,288 nextSource.scopeId,
280 options,289 options,
281 );290 );
282 }291 }
283 292
284 return yield* loadSourceByIdWithDeps(deps, {293 return yield* loadSourceByIdWithDeps(deps, {
285 workspaceId: nextSource.workspaceId,294 scopeId: nextSource.scopeId,
286 sourceId: nextSource.id,295 sourceId: nextSource.id,
287 actorAccountId: options.actorAccountId,296 actorScopeId: options.actorScopeId,
288 });297 });
289 }).pipe(298 }).pipe(
290 Effect.withSpan("source.store.persist", {299 Effect.withSpan("source.store.persist", {
291 attributes: {300 attributes: {
292 "executor.workspace.id": source.workspaceId,301 "executor.scope.id": source.scopeId,
293 "executor.source.id": source.id,302 "executor.source.id": source.id,
294 "executor.source.kind": source.kind,303 "executor.source.kind": source.kind,
295 "executor.source.status": source.status,304 "executor.source.status": source.status,
packages/platform/sdk/src/runtime/sources/source-store/records.ts+76−65
@@ -1,25 +1,36 @@
1import type {1import type {
2 AccountId,2 ScopeId,
3 AuthArtifact,3 AuthArtifact,
4 Source,4 Source,
5 SourceId,5 SourceId,
6 WorkspaceId,
7} from "#schema";6} from "#schema";
8import { SourceIdSchema } from "#schema";7import {
8 SourceIdSchema,
9} from "#schema";
9import * as Effect from "effect/Effect";10import * as Effect from "effect/Effect";
10 11
11import { sourceAuthFromAuthArtifact } from "../../auth/auth-artifacts";12import {
12import { authArtifactSecretMaterialRefs } from "../../auth/auth-artifacts";13 sourceAuthFromAuthArtifact,
13import type { LoadedLocalExecutorConfig } from "../../workspace-config";14} from "../../auth/auth-artifacts";
15import {
16 authArtifactSecretMaterialRefs,
17} from "../../auth/auth-artifacts";
18import type {
19 LoadedLocalExecutorConfig,
20} from "../../scope-config";
14import {21import {
15 LocalConfiguredSourceNotFoundError,22 LocalConfiguredSourceNotFoundError,
16 RuntimeLocalWorkspaceMismatchError,23 RuntimeLocalScopeMismatchError,
17 RuntimeLocalWorkspaceUnavailableError,24 RuntimeLocalScopeUnavailableError,
18} from "../../workspace-errors";25} from "../../scope-errors";
19import type { LocalWorkspaceState } from "../../workspace-state";26import type {
20import { getSourceAdapter } from "../source-adapters";27 LocalScopeState,
28} from "../../scope-state";
29import {
30 getSourceAdapter,
31} from "../source-adapters";
21import {32import {
22 resolveRuntimeLocalWorkspaceFromDeps,33 resolveRuntimeLocalScopeFromDeps,
23 type RuntimeSourceStoreDeps,34 type RuntimeSourceStoreDeps,
24} from "./deps";35} from "./deps";
25import {36import {
@@ -31,11 +42,11 @@ import { · 5 unmodified lines
31} from "./config";42} from "./config";
32 43
33export const buildLocalSourceRecord = (input: {44export const buildLocalSourceRecord = (input: {
34 workspaceId: WorkspaceId;45 scopeId: ScopeId;
35 loadedConfig: LoadedLocalExecutorConfig;46 loadedConfig: LoadedLocalExecutorConfig;
36 workspaceState: LocalWorkspaceState;47 scopeState: LocalScopeState;
37 sourceId: SourceId;48 sourceId: SourceId;
38 actorAccountId?: AccountId | null;49 actorScopeId?: ScopeId | null;
39 authArtifacts: ReadonlyArray<AuthArtifact>;50 authArtifacts: ReadonlyArray<AuthArtifact>;
40}): Effect.Effect<51}): Effect.Effect<
41 {52 {
@@ -54,11 +65,11 @@ export const buildLocalSourceRecord = (input: { · 12 unmodified lines
54 });65 });
55 }66 }
56 67
57 const existingState = input.workspaceState.sources[input.sourceId];68 const existingState = input.scopeState.sources[input.sourceId];
58 const adapter = getSourceAdapter(sourceConfig.kind);69 const adapter = getSourceAdapter(sourceConfig.kind);
59 const baseSource = (yield* adapter.validateSource({70 const baseSource = (yield* adapter.validateSource({
60 id: SourceIdSchema.make(input.sourceId),71 id: SourceIdSchema.make(input.sourceId),
61 workspaceId: input.workspaceId,72 scopeId: input.scopeId,
62 name: trimOrNull(sourceConfig.name) ?? input.sourceId,73 name: trimOrNull(sourceConfig.name) ?? input.sourceId,
63 kind: sourceConfig.kind,74 kind: sourceConfig.kind,
64 endpoint: sourceConfig.connection.endpoint.trim(),75 endpoint: sourceConfig.connection.endpoint.trim(),
@@ -86,14 +97,14 @@ export const buildLocalSourceRecord = (input: { · 21 unmodified lines
86 authArtifacts: input.authArtifacts.filter(97 authArtifacts: input.authArtifacts.filter(
87 (artifactItem) => artifactItem.sourceId === baseSource.id,98 (artifactItem) => artifactItem.sourceId === baseSource.id,
88 ),99 ),
89 actorAccountId: input.actorAccountId,100 actorScopeId: input.actorScopeId,
90 slot: "runtime",101 slot: "runtime",
91 });102 });
92 const importAuthArtifact = selectPreferredAuthArtifact({103 const importAuthArtifact = selectPreferredAuthArtifact({
93 authArtifacts: input.authArtifacts.filter(104 authArtifacts: input.authArtifacts.filter(
94 (artifactItem) => artifactItem.sourceId === baseSource.id,105 (artifactItem) => artifactItem.sourceId === baseSource.id,
95 ),106 ),
96 actorAccountId: input.actorAccountId,107 actorScopeId: input.actorScopeId,
97 slot: "import",108 slot: "import",
98 });109 });
99 110
@@ -119,36 +130,36 @@ export const buildLocalSourceRecord = (input: { · 19 unmodified lines
119 130
120export const loadSourcesInWorkspaceWithDeps = (131export const loadSourcesInWorkspaceWithDeps = (
121 deps: RuntimeSourceStoreDeps,132 deps: RuntimeSourceStoreDeps,
122 workspaceId: WorkspaceId,133 scopeId: ScopeId,
123 options: {134 options: {
124 actorAccountId?: AccountId | null;135 actorScopeId?: ScopeId | null;
125 } = {},136 } = {},
126): Effect.Effect<137): Effect.Effect<
127 readonly Source[],138 readonly Source[],
128 | RuntimeLocalWorkspaceUnavailableError139 | RuntimeLocalScopeUnavailableError
129 | RuntimeLocalWorkspaceMismatchError140 | RuntimeLocalScopeMismatchError
130 | LocalConfiguredSourceNotFoundError141 | LocalConfiguredSourceNotFoundError
131 | Error,142 | Error,
132 never143 never
133> =>144> =>
134 Effect.gen(function* () {145 Effect.gen(function* () {
135 const localWorkspace = yield* resolveRuntimeLocalWorkspaceFromDeps(146 const localScope = yield* resolveRuntimeLocalScopeFromDeps(
136 deps,147 deps,
137 workspaceId,148 scopeId,
138 );149 );
139 const authArtifacts = yield* deps.executorState.authArtifacts.listByWorkspaceId(150 const authArtifacts = yield* deps.executorState.authArtifacts.listByScopeId(
140 workspaceId,151 scopeId,
141 );152 );
142 const sources = yield* Effect.forEach(153 const sources = yield* Effect.forEach(
143 Object.keys(localWorkspace.loadedConfig.config?.sources ?? {}),154 Object.keys(localScope.loadedConfig.config?.sources ?? {}),
144 (sourceId) =>155 (sourceId) =>
145 Effect.map(156 Effect.map(
146 buildLocalSourceRecord({157 buildLocalSourceRecord({
147 workspaceId,158 scopeId,
148 loadedConfig: localWorkspace.loadedConfig,159 loadedConfig: localScope.loadedConfig,
149 workspaceState: localWorkspace.workspaceState,160 scopeState: localScope.scopeState,
150 sourceId: SourceIdSchema.make(sourceId),161 sourceId: SourceIdSchema.make(sourceId),
151 actorAccountId: options.actorAccountId,162 actorScopeId: options.actorScopeId,
152 authArtifacts,163 authArtifacts,
153 }),164 }),
154 ({ source }) => source,165 ({ source }) => source,
@@ -157,28 +168,28 @@ export const loadSourcesInWorkspaceWithDeps = ( · 2 unmodified lines
157 yield* Effect.annotateCurrentSpan("executor.source.count", sources.length);168 yield* Effect.annotateCurrentSpan("executor.source.count", sources.length);
158 return sources;169 return sources;
159 }).pipe(170 }).pipe(
160 Effect.withSpan("source.store.load_workspace", {171 Effect.withSpan("source.store.load_scope", {
161 attributes: {172 attributes: {
162 "executor.workspace.id": workspaceId,173 "executor.scope.id": scopeId,
163 },174 },
164 }),175 }),
165 );176 );
166 177
167export const syncWorkspaceSourceTypeDeclarationsWithDeps = (178export const syncScopeSourceTypeDeclarationsWithDeps = (
168 deps: RuntimeSourceStoreDeps,179 deps: RuntimeSourceStoreDeps,
169 workspaceId: WorkspaceId,180 scopeId: ScopeId,
170 options: {181 options: {
171 actorAccountId?: AccountId | null;182 actorScopeId?: ScopeId | null;
172 } = {},183 } = {},
173): Effect.Effect<void, Error, never> =>184): Effect.Effect<void, Error, never> =>
174 Effect.gen(function* () {185 Effect.gen(function* () {
175 const localWorkspace = yield* resolveRuntimeLocalWorkspaceFromDeps(186 const localScope = yield* resolveRuntimeLocalScopeFromDeps(
176 deps,187 deps,
177 workspaceId,188 scopeId,
178 );189 );
179 const sources = yield* loadSourcesInWorkspaceWithDeps(190 const sources = yield* loadSourcesInWorkspaceWithDeps(
180 deps,191 deps,
181 workspaceId,192 scopeId,
182 options,193 options,
183 );194 );
184 const entries = yield* Effect.forEach(sources, (source) =>195 const entries = yield* Effect.forEach(sources, (source) =>
@@ -202,14 +213,14 @@ export const syncWorkspaceSourceTypeDeclarationsWithDeps = ( · 17 unmodified lines
202 ),213 ),
203 });214 });
204 }).pipe(215 }).pipe(
205 Effect.withSpan("source.types.refresh_workspace.schedule", {216 Effect.withSpan("source.types.refresh_scope.schedule", {
206 attributes: {217 attributes: {
207 "executor.workspace.id": workspaceId,218 "executor.scope.id": scopeId,
208 },219 },
209 }),220 }),
210 );221 );
211 222
212export const shouldRefreshWorkspaceDeclarationsAfterPersist = (source: Source): boolean =>223export const shouldRefreshScopeDeclarationsAfterPersist = (source: Source): boolean =>
213 source.enabled === false ||224 source.enabled === false ||
214 source.status === "auth_required" ||225 source.status === "auth_required" ||
215 source.status === "error" ||226 source.status === "error" ||
@@ -217,24 +228,24 @@ export const shouldRefreshWorkspaceDeclarationsAfterPersist = (source: Source): · 1 unmodified line
217 228
218export const listLinkedSecretSourcesInWorkspaceWithDeps = (229export const listLinkedSecretSourcesInWorkspaceWithDeps = (
219 deps: RuntimeSourceStoreDeps,230 deps: RuntimeSourceStoreDeps,
220 workspaceId: WorkspaceId,231 scopeId: ScopeId,
221 options: {232 options: {
222 actorAccountId?: AccountId | null;233 actorScopeId?: ScopeId | null;
223 } = {},234 } = {},
224): Effect.Effect<235): Effect.Effect<
225 Map<string, Array<{ sourceId: string; sourceName: string }>>,236 Map<string, Array<{ sourceId: string; sourceName: string }>>,
226 | RuntimeLocalWorkspaceUnavailableError237 | RuntimeLocalScopeUnavailableError
227 | RuntimeLocalWorkspaceMismatchError238 | RuntimeLocalScopeMismatchError
228 | LocalConfiguredSourceNotFoundError239 | LocalConfiguredSourceNotFoundError
229 | Error,240 | Error,
230 never241 never
231> =>242> =>
232 Effect.gen(function* () {243 Effect.gen(function* () {
233 const [sources, authArtifacts, materialIds] = yield* Effect.all([244 const [sources, authArtifacts, materialIds] = yield* Effect.all([
234 loadSourcesInWorkspaceWithDeps(deps, workspaceId, {245 loadSourcesInWorkspaceWithDeps(deps, scopeId, {
235 actorAccountId: options.actorAccountId,246 actorScopeId: options.actorScopeId,
236 }),247 }),
237 deps.executorState.authArtifacts.listByWorkspaceId(workspaceId),248 deps.executorState.authArtifacts.listByScopeId(scopeId),
238 deps.executorState.secretMaterials.listAll().pipe(249 deps.executorState.secretMaterials.listAll().pipe(
239 Effect.map(250 Effect.map(
240 (materials) => new Set(materials.map((material) => String(material.id))),251 (materials) => new Set(materials.map((material) => String(material.id))),
@@ -273,39 +284,39 @@ export const listLinkedSecretSourcesInWorkspaceWithDeps = ( · 32 unmodified lines
273export const loadSourceByIdWithDeps = (284export const loadSourceByIdWithDeps = (
274 deps: RuntimeSourceStoreDeps,285 deps: RuntimeSourceStoreDeps,
275 input: {286 input: {
276 workspaceId: WorkspaceId;287 scopeId: ScopeId;
277 sourceId: Source["id"];288 sourceId: Source["id"];
278 actorAccountId?: AccountId | null;289 actorScopeId?: ScopeId | null;
279 },290 },
280): Effect.Effect<291): Effect.Effect<
281 Source,292 Source,
282 | RuntimeLocalWorkspaceUnavailableError293 | RuntimeLocalScopeUnavailableError
283 | RuntimeLocalWorkspaceMismatchError294 | RuntimeLocalScopeMismatchError
284 | LocalConfiguredSourceNotFoundError295 | LocalConfiguredSourceNotFoundError
285 | Error,296 | Error,
286 never297 never
287> =>298> =>
288 Effect.gen(function* () {299 Effect.gen(function* () {
289 const localWorkspace = yield* resolveRuntimeLocalWorkspaceFromDeps(300 const localScope = yield* resolveRuntimeLocalScopeFromDeps(
290 deps,301 deps,
291 input.workspaceId,302 input.scopeId,
292 );303 );
293 const authArtifacts = yield* deps.executorState.authArtifacts.listByWorkspaceId(304 const authArtifacts = yield* deps.executorState.authArtifacts.listByScopeId(
294 input.workspaceId,305 input.scopeId,
295 );306 );
296 if (!localWorkspace.loadedConfig.config?.sources?.[input.sourceId]) {307 if (!localScope.loadedConfig.config?.sources?.[input.sourceId]) {
297 return yield* new LocalConfiguredSourceNotFoundError({308 return yield* new LocalConfiguredSourceNotFoundError({
298 message: `Source not found: workspaceId=${input.workspaceId} sourceId=${input.sourceId}`,309 message: `Source not found: scopeId=${input.scopeId} sourceId=${input.sourceId}`,
299 sourceId: input.sourceId,310 sourceId: input.sourceId,
300 });311 });
301 }312 }
302 313
303 const localSource = yield* buildLocalSourceRecord({314 const localSource = yield* buildLocalSourceRecord({
304 workspaceId: input.workspaceId,315 scopeId: input.scopeId,
305 loadedConfig: localWorkspace.loadedConfig,316 loadedConfig: localScope.loadedConfig,
306 workspaceState: localWorkspace.workspaceState,317 scopeState: localScope.scopeState,
307 sourceId: input.sourceId,318 sourceId: input.sourceId,
308 actorAccountId: input.actorAccountId,319 actorScopeId: input.actorScopeId,
309 authArtifacts,320 authArtifacts,
310 });321 });
311 322
@@ -313,7 +324,7 @@ export const loadSourceByIdWithDeps = ( · 1 unmodified line
313 }).pipe(324 }).pipe(
314 Effect.withSpan("source.store.load_by_id", {325 Effect.withSpan("source.store.load_by_id", {
315 attributes: {326 attributes: {
316 "executor.workspace.id": input.workspaceId,327 "executor.scope.id": input.scopeId,
317 "executor.source.id": input.sourceId,328 "executor.source.id": input.sourceId,
318 },329 },
319 }),330 }),
packages/platform/sdk/src/runtime/workspace/runtime-context.ts+0−78
@@ -1,78 +0,0 @@
1import * as Context from "effect/Context";
2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4import * as Option from "effect/Option";
5
6import type { AccountId, WorkspaceId } from "#schema";
7import type { ExecutorWorkspaceContext } from "../../workspace";
8import type { LoadedLocalExecutorConfig } from "../workspace-config";
9import {
10 RuntimeLocalWorkspaceMismatchError,
11 RuntimeLocalWorkspaceUnavailableError,
12} from "../workspace-errors";
13
14export type RuntimeLocalWorkspaceState = {
15 workspace: ExecutorWorkspaceContext;
16 installation: {
17 workspaceId: WorkspaceId;
18 accountId: AccountId;
19 };
20 loadedConfig: LoadedLocalExecutorConfig;
21};
22
23export class RuntimeLocalWorkspaceService extends Context.Tag(
24 "#runtime/RuntimeLocalWorkspaceService",
25)<RuntimeLocalWorkspaceService, RuntimeLocalWorkspaceState>() {}
26
27export const RuntimeLocalWorkspaceLive = (
28 runtimeLocalWorkspace: RuntimeLocalWorkspaceState,
29) => Layer.succeed(RuntimeLocalWorkspaceService, runtimeLocalWorkspace);
30
31export const provideOptionalRuntimeLocalWorkspace = <A, E, R>(
32 effect: Effect.Effect<A, E, R>,
33 runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null | undefined,
34): Effect.Effect<A, E, R> =>
35 runtimeLocalWorkspace === null || runtimeLocalWorkspace === undefined
36 ? effect
37 : effect.pipe(Effect.provide(RuntimeLocalWorkspaceLive(runtimeLocalWorkspace)));
38
39export const getRuntimeLocalWorkspaceOption = () =>
40 Effect.contextWith((context) =>
41 Context.getOption(context, RuntimeLocalWorkspaceService),
42 ).pipe(
43 Effect.map((option) => (Option.isSome(option) ? option.value : null)),
44 ) as Effect.Effect<RuntimeLocalWorkspaceState | null, never, never>;
45
46export const requireRuntimeLocalWorkspace = (
47 workspaceId?: WorkspaceId,
48): Effect.Effect<
49 RuntimeLocalWorkspaceState,
50 RuntimeLocalWorkspaceUnavailableError | RuntimeLocalWorkspaceMismatchError,
51 never
52> =>
53 Effect.gen(function* () {
54 const runtimeLocalWorkspace = yield* getRuntimeLocalWorkspaceOption();
55 if (runtimeLocalWorkspace === null) {
56 return yield* new RuntimeLocalWorkspaceUnavailableError({
57 message: "Runtime local workspace is unavailable",
58 });
59 }
60
61 if (
62 workspaceId !== undefined
63 && runtimeLocalWorkspace.installation.workspaceId !== workspaceId
64 ) {
65 return yield* new RuntimeLocalWorkspaceMismatchError({
66 message: `Workspace ${workspaceId} is not the active local workspace ${runtimeLocalWorkspace.installation.workspaceId}`,
67 requestedWorkspaceId: workspaceId,
68 activeWorkspaceId: runtimeLocalWorkspace.installation.workspaceId,
69 });
70 }
71
72 return runtimeLocalWorkspace;
73 });
74
75export const requireRuntimeLocalAccountId = (workspaceId?: WorkspaceId) =>
76 requireRuntimeLocalWorkspace(workspaceId).pipe(
77 Effect.map((runtimeLocalWorkspace) => runtimeLocalWorkspace.installation.accountId),
78 );
packages/platform/sdk/src/schema/common.ts+3−1
@@ -1,4 +1,6 @@
1import { Schema } from "effect";1import {
2 Schema,
3} from "effect";
2 4
3export const TimestampMsSchema = Schema.Number;5export const TimestampMsSchema = Schema.Number;
4 6
packages/platform/sdk/src/schema/enums.ts+4−4
@@ -54,8 +54,8 @@ export { · 53 unmodified lines
54 type SourceAuthSessionStatus,54 type SourceAuthSessionStatus,
55} from "./models/source-auth-session";55} from "./models/source-auth-session";
56export {56export {
57 LocalWorkspacePolicyApprovalModeSchema,57 LocalScopePolicyApprovalModeSchema,
58 LocalWorkspacePolicyEffectSchema,58 LocalScopePolicyEffectSchema,
59 type LocalWorkspacePolicyApprovalMode,59 type LocalScopePolicyApprovalMode,
60 type LocalWorkspacePolicyEffect,60 type LocalScopePolicyEffect,
61} from "./models/policy";61} from "./models/policy";
packages/platform/sdk/src/schema/ids.ts+11−11
@@ -1,7 +1,8 @@
1import { Schema } from "effect";1import {
2 Schema,
3} from "effect";
2 4
3export const AccountIdSchema = Schema.String.pipe(Schema.brand("AccountId"));5export const ScopeIdSchema = Schema.String.pipe(Schema.brand("ScopeId"));
4export const WorkspaceIdSchema = Schema.String.pipe(Schema.brand("WorkspaceId"));
5export const SourceIdSchema = Schema.String.pipe(Schema.brand("SourceId"));6export const SourceIdSchema = Schema.String.pipe(Schema.brand("SourceId"));
6export const SourceCatalogIdSchema = Schema.String.pipe(Schema.brand("SourceCatalogId"));7export const SourceCatalogIdSchema = Schema.String.pipe(Schema.brand("SourceCatalogId"));
7export const SourceCatalogRevisionIdSchema = Schema.String.pipe(8export const SourceCatalogRevisionIdSchema = Schema.String.pipe(
@@ -17,11 +18,11 @@ export const AuthLeaseIdSchema = Schema.String.pipe( · 9 unmodified lines
17 Schema.brand("AuthLeaseId"),18 Schema.brand("AuthLeaseId"),
18);19);
19export const CredentialIdSchema = AuthArtifactIdSchema;20export const CredentialIdSchema = AuthArtifactIdSchema;
20export const WorkspaceSourceOauthClientIdSchema = Schema.String.pipe(21export const ScopedSourceOauthClientIdSchema = Schema.String.pipe(
21 Schema.brand("WorkspaceSourceOauthClientId"),22 Schema.brand("ScopedSourceOauthClientId"),
22);23);
23export const WorkspaceOauthClientIdSchema = Schema.String.pipe(24export const ScopeOauthClientIdSchema = Schema.String.pipe(
24 Schema.brand("WorkspaceOauthClientId"),25 Schema.brand("ScopeOauthClientId"),
25);26);
26export const ProviderAuthGrantIdSchema = Schema.String.pipe(27export const ProviderAuthGrantIdSchema = Schema.String.pipe(
27 Schema.brand("ProviderAuthGrantId"),28 Schema.brand("ProviderAuthGrantId"),
@@ -38,8 +39,7 @@ export const ExecutionStepIdSchema = Schema.String.pipe( · 10 unmodified lines
38 Schema.brand("ExecutionStepId"),39 Schema.brand("ExecutionStepId"),
39);40);
40 41
41export type AccountId = typeof AccountIdSchema.Type;42export type ScopeId = typeof ScopeIdSchema.Type;
42export type WorkspaceId = typeof WorkspaceIdSchema.Type;
43export type SourceId = typeof SourceIdSchema.Type;43export type SourceId = typeof SourceIdSchema.Type;
44export type SourceCatalogId = typeof SourceCatalogIdSchema.Type;44export type SourceCatalogId = typeof SourceCatalogIdSchema.Type;
45export type SourceCatalogRevisionId = typeof SourceCatalogRevisionIdSchema.Type;45export type SourceCatalogRevisionId = typeof SourceCatalogRevisionIdSchema.Type;
@@ -47,8 +47,8 @@ export type SourceAuthSessionId = typeof SourceAuthSessionIdSchema.Type; · 1 unmodified line
47export type AuthArtifactId = typeof AuthArtifactIdSchema.Type;47export type AuthArtifactId = typeof AuthArtifactIdSchema.Type;
48export type AuthLeaseId = typeof AuthLeaseIdSchema.Type;48export type AuthLeaseId = typeof AuthLeaseIdSchema.Type;
49export type CredentialId = typeof CredentialIdSchema.Type;49export type CredentialId = typeof CredentialIdSchema.Type;
50export type WorkspaceSourceOauthClientId = typeof WorkspaceSourceOauthClientIdSchema.Type;50export type ScopedSourceOauthClientId = typeof ScopedSourceOauthClientIdSchema.Type;
51export type WorkspaceOauthClientId = typeof WorkspaceOauthClientIdSchema.Type;51export type ScopeOauthClientId = typeof ScopeOauthClientIdSchema.Type;
52export type ProviderAuthGrantId = typeof ProviderAuthGrantIdSchema.Type;52export type ProviderAuthGrantId = typeof ProviderAuthGrantIdSchema.Type;
53export type SecretMaterialId = typeof SecretMaterialIdSchema.Type;53export type SecretMaterialId = typeof SecretMaterialIdSchema.Type;
54export type PolicyId = typeof PolicyIdSchema.Type;54export type PolicyId = typeof PolicyIdSchema.Type;
packages/platform/sdk/src/schema/index.ts+1−1
@@ -7,7 +7,7 @@ export * from "./models/source-catalog"; · 6 unmodified lines
7export * from "./models/auth-artifact";7export * from "./models/auth-artifact";
8export * from "./models/auth-lease";8export * from "./models/auth-lease";
9export * from "./models/source-oauth-client";9export * from "./models/source-oauth-client";
10export * from "./models/workspace-oauth-client";10export * from "./models/scope-oauth-client";
11export * from "./models/provider-auth-grant";11export * from "./models/provider-auth-grant";
12export * from "./models/secret-material";12export * from "./models/secret-material";
13export * from "./models/source-auth-session";13export * from "./models/source-auth-session";
packages/platform/sdk/src/schema/models/auth-artifact.ts+9−6
@@ -1,13 +1,16 @@
1import { Schema } from "effect";1import {
2 Schema,
3} from "effect";
2import * as Option from "effect/Option";4import * as Option from "effect/Option";
3 5
4import { TimestampMsSchema } from "../common";
5import {6import {
6 AccountIdSchema,7 TimestampMsSchema,
8} from "../common";
9import {
10 ScopeIdSchema,
7 AuthArtifactIdSchema,11 AuthArtifactIdSchema,
8 ProviderAuthGrantIdSchema,12 ProviderAuthGrantIdSchema,
9 SourceIdSchema,13 SourceIdSchema,
10 WorkspaceIdSchema,
11} from "../ids";14} from "../ids";
12 15
13export const SecretRefSchema = Schema.Struct({16export const SecretRefSchema = Schema.Struct({
@@ -207,9 +210,9 @@ const decodeAuthGrantSetOption = Schema.decodeUnknownOption(AuthGrantSetJsonSche · 193 unmodified lines
207 210
208export const AuthArtifactSchema = Schema.Struct({211export const AuthArtifactSchema = Schema.Struct({
209 id: AuthArtifactIdSchema,212 id: AuthArtifactIdSchema,
210 workspaceId: WorkspaceIdSchema,213 scopeId: ScopeIdSchema,
211 sourceId: SourceIdSchema,214 sourceId: SourceIdSchema,
212 actorAccountId: Schema.NullOr(AccountIdSchema),215 actorScopeId: Schema.NullOr(ScopeIdSchema),
213 slot: AuthArtifactSlotSchema,216 slot: AuthArtifactSlotSchema,
214 artifactKind: AuthArtifactKindSchema,217 artifactKind: AuthArtifactKindSchema,
215 configJson: Schema.String,218 configJson: Schema.String,
packages/platform/sdk/src/schema/models/auth-lease.ts+9−6
@@ -1,13 +1,16 @@
1import { Schema } from "effect";1import {
2 Schema,
3} from "effect";
2import * as Option from "effect/Option";4import * as Option from "effect/Option";
3 5
4import { TimestampMsSchema } from "../common";
5import {6import {
6 AccountIdSchema,7 TimestampMsSchema,
8} from "../common";
9import {
10 ScopeIdSchema,
7 AuthArtifactIdSchema,11 AuthArtifactIdSchema,
8 AuthLeaseIdSchema,12 AuthLeaseIdSchema,
9 SourceIdSchema,13 SourceIdSchema,
10 WorkspaceIdSchema,
11} from "../ids";14} from "../ids";
12 15
13import {16import {
@@ -19,9 +22,9 @@ import { · 5 unmodified lines
19export const AuthLeaseSchema = Schema.Struct({22export const AuthLeaseSchema = Schema.Struct({
20 id: AuthLeaseIdSchema,23 id: AuthLeaseIdSchema,
21 authArtifactId: AuthArtifactIdSchema,24 authArtifactId: AuthArtifactIdSchema,
22 workspaceId: WorkspaceIdSchema,25 scopeId: ScopeIdSchema,
23 sourceId: SourceIdSchema,26 sourceId: SourceIdSchema,
24 actorAccountId: Schema.NullOr(AccountIdSchema),27 actorScopeId: Schema.NullOr(ScopeIdSchema),
25 slot: AuthArtifactSlotSchema,28 slot: AuthArtifactSlotSchema,
26 placementsTemplateJson: Schema.String,29 placementsTemplateJson: Schema.String,
27 expiresAt: Schema.NullOr(TimestampMsSchema),30 expiresAt: Schema.NullOr(TimestampMsSchema),
packages/platform/sdk/src/schema/models/code-migration.ts+6−2
@@ -1,6 +1,10 @@
1import { Schema } from "effect";1import {
2 Schema,
3} from "effect";
2 4
3import { TimestampMsSchema } from "../common";5import {
6 TimestampMsSchema,
7} from "../common";
4 8
5export const StoredCodeMigrationRecordSchema = Schema.Struct({9export const StoredCodeMigrationRecordSchema = Schema.Struct({
6 id: Schema.String,10 id: Schema.String,
packages/platform/sdk/src/schema/models/execution.ts+9−6
@@ -1,12 +1,15 @@
1import { Schema } from "effect";1import {
2 Schema,
3} from "effect";
2 4
3import { TimestampMsSchema } from "../common";
4import {5import {
5 AccountIdSchema,6 TimestampMsSchema,
7} from "../common";
8import {
6 ExecutionIdSchema,9 ExecutionIdSchema,
7 ExecutionInteractionIdSchema,10 ExecutionInteractionIdSchema,
8 ExecutionStepIdSchema,11 ExecutionStepIdSchema,
9 WorkspaceIdSchema,12 ScopeIdSchema,
10} from "../ids";13} from "../ids";
11 14
12export const ExecutionStatusSchema = Schema.Literal(15export const ExecutionStatusSchema = Schema.Literal(
@@ -20,8 +23,8 @@ export const ExecutionStatusSchema = Schema.Literal( · 7 unmodified lines
20 23
21export const ExecutionSchema = Schema.Struct({24export const ExecutionSchema = Schema.Struct({
22 id: ExecutionIdSchema,25 id: ExecutionIdSchema,
23 workspaceId: WorkspaceIdSchema,26 scopeId: ScopeIdSchema,
24 createdByAccountId: AccountIdSchema,27 createdByScopeId: ScopeIdSchema,
25 status: ExecutionStatusSchema,28 status: ExecutionStatusSchema,
26 code: Schema.String,29 code: Schema.String,
27 resultJson: Schema.NullOr(Schema.String),30 resultJson: Schema.NullOr(Schema.String),
packages/platform/sdk/src/schema/models/local-config.ts+6−2
@@ -1,6 +1,10 @@
1import { Schema } from "effect";1import {
2 Schema,
3} from "effect";
2 4
3import { localConfigurableSourceAdapters } from "../../runtime/sources/source-adapters";5import {
6 localConfigurableSourceAdapters,
7} from "../../runtime/sources/source-adapters";
4 8
5export const LocalExecutorRuntimeSchema = Schema.Literal(9export const LocalExecutorRuntimeSchema = Schema.Literal(
6 "quickjs",10 "quickjs",
packages/platform/sdk/src/schema/models/local-installation.ts+4−4
@@ -1,13 +1,13 @@
1import * as Schema from "effect/Schema";1import * as Schema from "effect/Schema";
2 2
3import {3import {
4 AccountIdSchema,4 ScopeIdSchema,
5 WorkspaceIdSchema,
6} from "../ids";5} from "../ids";
7 6
8export const LocalInstallationSchema = Schema.Struct({7export const LocalInstallationSchema = Schema.Struct({
9 accountId: AccountIdSchema,8 scopeId: ScopeIdSchema,
10 workspaceId: WorkspaceIdSchema,9 actorScopeId: ScopeIdSchema,
10 resolutionScopeIds: Schema.Array(ScopeIdSchema),
11});11});
12 12
13export const LocalInstallationInsertSchema = LocalInstallationSchema;13export const LocalInstallationInsertSchema = LocalInstallationSchema;
packages/platform/sdk/src/schema/models/policy.ts+22−15
@@ -1,28 +1,35 @@
1import { Schema } from "effect";1import {
2 Schema,
3} from "effect";
2 4
3import { TimestampMsSchema } from "../common";5import {
4import { PolicyIdSchema, WorkspaceIdSchema } from "../ids";6 TimestampMsSchema,
7} from "../common";
8import {
9 PolicyIdSchema,
10 ScopeIdSchema,
11} from "../ids";
5 12
6export const LocalWorkspacePolicyEffectSchema = Schema.Literal("allow", "deny");13export const LocalScopePolicyEffectSchema = Schema.Literal("allow", "deny");
7export const LocalWorkspacePolicyApprovalModeSchema = Schema.Literal("auto", "required");14export const LocalScopePolicyApprovalModeSchema = Schema.Literal("auto", "required");
8 15
9export const LocalWorkspacePolicySchema = Schema.Struct({16export const LocalScopePolicySchema = Schema.Struct({
10 id: PolicyIdSchema,17 id: PolicyIdSchema,
11 key: Schema.String,18 key: Schema.String,
12 workspaceId: WorkspaceIdSchema,19 scopeId: ScopeIdSchema,
13 resourcePattern: Schema.String,20 resourcePattern: Schema.String,
14 effect: LocalWorkspacePolicyEffectSchema,21 effect: LocalScopePolicyEffectSchema,
15 approvalMode: LocalWorkspacePolicyApprovalModeSchema,22 approvalMode: LocalScopePolicyApprovalModeSchema,
16 priority: Schema.Number,23 priority: Schema.Number,
17 enabled: Schema.Boolean,24 enabled: Schema.Boolean,
18 createdAt: TimestampMsSchema,25 createdAt: TimestampMsSchema,
19 updatedAt: TimestampMsSchema,26 updatedAt: TimestampMsSchema,
20});27});
21 28
22export const LocalWorkspacePolicyInsertSchema = LocalWorkspacePolicySchema;29export const LocalScopePolicyInsertSchema = LocalScopePolicySchema;
23export const LocalWorkspacePolicyUpdateSchema = Schema.partial(LocalWorkspacePolicySchema);30export const LocalScopePolicyUpdateSchema = Schema.partial(LocalScopePolicySchema);
24 31
25export type LocalWorkspacePolicyEffect = typeof LocalWorkspacePolicyEffectSchema.Type;32export type LocalScopePolicyEffect = typeof LocalScopePolicyEffectSchema.Type;
26export type LocalWorkspacePolicyApprovalMode =33export type LocalScopePolicyApprovalMode =
27 typeof LocalWorkspacePolicyApprovalModeSchema.Type;34 typeof LocalScopePolicyApprovalModeSchema.Type;
28export type LocalWorkspacePolicy = typeof LocalWorkspacePolicySchema.Type;35export type LocalScopePolicy = typeof LocalScopePolicySchema.Type;
packages/platform/sdk/src/schema/models/provider-auth-grant.ts+11−8
@@ -1,11 +1,14 @@
1import { Schema } from "effect";1import {
2 Schema,
3} from "effect";
2 4
3import { TimestampMsSchema } from "../common";
4import {5import {
5 AccountIdSchema,6 TimestampMsSchema,
7} from "../common";
8import {
9 ScopeIdSchema,
6 ProviderAuthGrantIdSchema,10 ProviderAuthGrantIdSchema,
7 WorkspaceIdSchema,11 ScopeOauthClientIdSchema,
8 WorkspaceOauthClientIdSchema,
9} from "../ids";12} from "../ids";
10 13
11import {14import {
@@ -15,10 +18,10 @@ import { · 3 unmodified lines
15 18
16export const ProviderAuthGrantSchema = Schema.Struct({19export const ProviderAuthGrantSchema = Schema.Struct({
17 id: ProviderAuthGrantIdSchema,20 id: ProviderAuthGrantIdSchema,
18 workspaceId: WorkspaceIdSchema,21 scopeId: ScopeIdSchema,
19 actorAccountId: Schema.NullOr(AccountIdSchema),22 actorScopeId: Schema.NullOr(ScopeIdSchema),
20 providerKey: Schema.String,23 providerKey: Schema.String,
21 oauthClientId: WorkspaceOauthClientIdSchema,24 oauthClientId: ScopeOauthClientIdSchema,
22 tokenEndpoint: Schema.String,25 tokenEndpoint: Schema.String,
23 clientAuthentication: OAuth2ClientAuthenticationMethodSchema,26 clientAuthentication: OAuth2ClientAuthenticationMethodSchema,
24 headerName: Schema.String,27 headerName: Schema.String,
packages/platform/sdk/src/schema/models/scope-oauth-client.tsrenamed from packages/platform/sdk/src/schema/models/workspace-oauth-client.ts+12−8
@@ -1,14 +1,18 @@
1import { Schema } from "effect";1import {
2 Schema,
3} from "effect";
2 4
3import { TimestampMsSchema } from "../common";
4import {5import {
5 WorkspaceIdSchema,6 TimestampMsSchema,
6 WorkspaceOauthClientIdSchema,7} from "../common";
8import {
9 ScopeIdSchema,
10 ScopeOauthClientIdSchema,
7} from "../ids";11} from "../ids";
8 12
9export const WorkspaceOauthClientSchema = Schema.Struct({13export const ScopeOauthClientSchema = Schema.Struct({
10 id: WorkspaceOauthClientIdSchema,14 id: ScopeOauthClientIdSchema,
11 workspaceId: WorkspaceIdSchema,15 scopeId: ScopeIdSchema,
12 providerKey: Schema.String,16 providerKey: Schema.String,
13 label: Schema.NullOr(Schema.String),17 label: Schema.NullOr(Schema.String),
14 clientId: Schema.String,18 clientId: Schema.String,
@@ -19,4 +23,4 @@ export const WorkspaceOauthClientSchema = Schema.Struct({ · 4 unmodified lines
19 updatedAt: TimestampMsSchema,23 updatedAt: TimestampMsSchema,
20});24});
21 25
22export type WorkspaceOauthClient = typeof WorkspaceOauthClientSchema.Type;26export type ScopeOauthClient = typeof ScopeOauthClientSchema.Type;
packages/platform/sdk/src/schema/models/secret-material.ts+9−3
@@ -1,7 +1,13 @@
1import { Schema } from "effect";1import {
2 Schema,
3} from "effect";
2 4
3import { TimestampMsSchema } from "../common";5import {
4import { SecretMaterialIdSchema } from "../ids";6 TimestampMsSchema,
7} from "../common";
8import {
9 SecretMaterialIdSchema,
10} from "../ids";
5 11
6export const SecretMaterialPurposeSchema = Schema.Literal(12export const SecretMaterialPurposeSchema = Schema.Literal(
7 "auth_material",13 "auth_material",
packages/platform/sdk/src/schema/models/source-auth-session.ts+11−8
@@ -1,14 +1,17 @@
1import { Schema } from "effect";1import {
2 Schema,
3} from "effect";
2 4
3import { TimestampMsSchema } from "../common";
4import {5import {
5 AccountIdSchema,6 TimestampMsSchema,
7} from "../common";
8import {
9 ScopeIdSchema,
6 ExecutionIdSchema,10 ExecutionIdSchema,
7 ExecutionInteractionIdSchema,11 ExecutionInteractionIdSchema,
8 SourceIdSchema,12 SourceIdSchema,
9 SourceAuthSessionIdSchema,13 SourceAuthSessionIdSchema,
10 WorkspaceOauthClientIdSchema,14 ScopeOauthClientIdSchema,
11 WorkspaceIdSchema,
12} from "../ids";15} from "../ids";
13import {16import {
14 CredentialSlotSchema,17 CredentialSlotSchema,
@@ -109,7 +112,7 @@ export const ProviderOauthBatchSourceAuthSessionDataSchema = Schema.Struct({ · 94 unmodified lines
109 authorizationEndpoint: Schema.String,112 authorizationEndpoint: Schema.String,
110 tokenEndpoint: Schema.String,113 tokenEndpoint: Schema.String,
111 redirectUri: Schema.String,114 redirectUri: Schema.String,
112 oauthClientId: WorkspaceOauthClientIdSchema,115 oauthClientId: ScopeOauthClientIdSchema,
113 clientAuthentication: OAuth2ClientAuthenticationMethodSchema,116 clientAuthentication: OAuth2ClientAuthenticationMethodSchema,
114 scopes: Schema.Array(Schema.String),117 scopes: Schema.Array(Schema.String),
115 headerName: Schema.String,118 headerName: Schema.String,
@@ -129,9 +132,9 @@ export const ProviderOauthBatchSourceAuthSessionDataJsonSchema = Schema.parseJso · 13 unmodified lines
129 132
130export const SourceAuthSessionSchema = Schema.Struct({133export const SourceAuthSessionSchema = Schema.Struct({
131 id: SourceAuthSessionIdSchema,134 id: SourceAuthSessionIdSchema,
132 workspaceId: WorkspaceIdSchema,135 scopeId: ScopeIdSchema,
133 sourceId: SourceIdSchema,136 sourceId: SourceIdSchema,
134 actorAccountId: Schema.NullOr(AccountIdSchema),137 actorScopeId: Schema.NullOr(ScopeIdSchema),
135 credentialSlot: CredentialSlotSchema,138 credentialSlot: CredentialSlotSchema,
136 executionId: Schema.NullOr(ExecutionIdSchema),139 executionId: Schema.NullOr(ExecutionIdSchema),
137 interactionId: Schema.NullOr(ExecutionInteractionIdSchema),140 interactionId: Schema.NullOr(ExecutionInteractionIdSchema),
packages/platform/sdk/src/schema/models/source-catalog.ts+7−3
@@ -1,6 +1,10 @@
1import { Schema } from "effect";1import {
2 Schema,
3} from "effect";
2 4
3import { TimestampMsSchema } from "../common";5import {
6 TimestampMsSchema,
7} from "../common";
4import {8import {
5 SourceCatalogIdSchema,9 SourceCatalogIdSchema,
6 SourceCatalogRevisionIdSchema,10 SourceCatalogRevisionIdSchema,
@@ -15,7 +19,7 @@ export const SourceCatalogAdapterKeySchema = Schema.String; · 8 unmodified lines
15 19
16export const SourceCatalogVisibilitySchema = Schema.Literal(20export const SourceCatalogVisibilitySchema = Schema.Literal(
17 "private",21 "private",
18 "workspace",22 "scope",
19 "organization",23 "organization",
20 "public",24 "public",
21);25);
packages/platform/sdk/src/schema/models/source-inspection.ts+6−2
@@ -1,6 +1,10 @@
1import { Schema } from "effect";1import {
2 Schema,
3} from "effect";
2 4
3import { SourceSchema } from "./source";5import {
6 SourceSchema,
7} from "./source";
4 8
5export const SourceInspectionPipelineKindSchema = Schema.Literal("ir");9export const SourceInspectionPipelineKindSchema = Schema.Literal("ir");
6 10
packages/platform/sdk/src/schema/models/source-oauth-client.ts+22−18
@@ -1,31 +1,35 @@
1import { Schema } from "effect";1import {
2 Schema,
3} from "effect";
2export {4export {
3 SourceOauthClientInputSchema,5 SourceOauthClientInputSchema,
4 WorkspaceSourceOauthClientRedirectModeSchema,6 ScopedSourceOauthClientRedirectModeSchema,
5} from "@executor/source-core";7} from "@executor/source-core";
6import {8import {
7 SourceOauthClientInputSchema,9 SourceOauthClientInputSchema,
8 WorkspaceSourceOauthClientRedirectModeSchema,10 ScopedSourceOauthClientRedirectModeSchema,
9} from "@executor/source-core";11} from "@executor/source-core";
10 12
11import { TimestampMsSchema } from "../common";13import {
14 TimestampMsSchema,
15} from "../common";
12import {16import {
13 SourceIdSchema,17 SourceIdSchema,
14 WorkspaceIdSchema,18 ScopeIdSchema,
15 WorkspaceSourceOauthClientIdSchema,19 ScopedSourceOauthClientIdSchema,
16} from "../ids";20} from "../ids";
17 21
18export const WorkspaceSourceOauthClientMetadataSchema = Schema.Struct({22export const ScopedSourceOauthClientMetadataSchema = Schema.Struct({
19 redirectMode: Schema.optional(WorkspaceSourceOauthClientRedirectModeSchema),23 redirectMode: Schema.optional(ScopedSourceOauthClientRedirectModeSchema),
20});24});
21 25
22export const WorkspaceSourceOauthClientMetadataJsonSchema = Schema.parseJson(26export const ScopedSourceOauthClientMetadataJsonSchema = Schema.parseJson(
23 WorkspaceSourceOauthClientMetadataSchema,27 ScopedSourceOauthClientMetadataSchema,
24);28);
25 29
26export const WorkspaceSourceOauthClientSchema = Schema.Struct({30export const ScopedSourceOauthClientSchema = Schema.Struct({
27 id: WorkspaceSourceOauthClientIdSchema,31 id: ScopedSourceOauthClientIdSchema,
28 workspaceId: WorkspaceIdSchema,32 scopeId: ScopeIdSchema,
29 sourceId: SourceIdSchema,33 sourceId: SourceIdSchema,
30 providerKey: Schema.String,34 providerKey: Schema.String,
31 clientId: Schema.String,35 clientId: Schema.String,
@@ -36,9 +40,9 @@ export const WorkspaceSourceOauthClientSchema = Schema.Struct({ · 4 unmodified lines
36 updatedAt: TimestampMsSchema,40 updatedAt: TimestampMsSchema,
37});41});
38 42
39export type WorkspaceSourceOauthClient = typeof WorkspaceSourceOauthClientSchema.Type;43export type ScopedSourceOauthClient = typeof ScopedSourceOauthClientSchema.Type;
40export type WorkspaceSourceOauthClientRedirectMode =44export type ScopedSourceOauthClientRedirectMode =
41 typeof WorkspaceSourceOauthClientRedirectModeSchema.Type;45 typeof ScopedSourceOauthClientRedirectModeSchema.Type;
42export type WorkspaceSourceOauthClientMetadata =46export type ScopedSourceOauthClientMetadata =
43 typeof WorkspaceSourceOauthClientMetadataSchema.Type;47 typeof ScopedSourceOauthClientMetadataSchema.Type;
44export type SourceOauthClientInput = typeof SourceOauthClientInputSchema.Type;48export type SourceOauthClientInput = typeof SourceOauthClientInputSchema.Type;
packages/platform/sdk/src/schema/models/source.ts+18−10
@@ -1,4 +1,6 @@
1import { Schema } from "effect";1import {
2 Schema,
3} from "effect";
2export {4export {
3 SourceImportAuthPolicySchema,5 SourceImportAuthPolicySchema,
4 SourceTransportSchema,6 SourceTransportSchema,
@@ -10,16 +12,22 @@ import { · 5 unmodified lines
10 StringMapSchema,12 StringMapSchema,
11} from "@executor/source-core";13} from "@executor/source-core";
12 14
13import { TimestampMsSchema } from "../common";15import {
16 TimestampMsSchema,
17} from "../common";
14import {18import {
15 ProviderAuthGrantIdSchema,19 ProviderAuthGrantIdSchema,
16 SourceIdSchema,20 SourceIdSchema,
17 SourceCatalogIdSchema,21 SourceCatalogIdSchema,
18 SourceCatalogRevisionIdSchema,22 SourceCatalogRevisionIdSchema,
19 WorkspaceIdSchema,23 ScopeIdSchema,
20} from "../ids";24} from "../ids";
21import { SecretRefSchema } from "./auth-artifact";25import {
22import { JsonObjectSchema } from "./source-auth-session";26 SecretRefSchema,
27} from "./auth-artifact";
28import {
29 JsonObjectSchema,
30} from "./source-auth-session";
23 31
24export const SourceKindSchema = Schema.String;32export const SourceKindSchema = Schema.String;
25 33
@@ -91,7 +99,7 @@ export const SourceBindingSchema = Schema.Struct({ · 65 unmodified lines
91});99});
92 100
93const SourceStorageRowSchema = Schema.Struct({101const SourceStorageRowSchema = Schema.Struct({
94 workspaceId: WorkspaceIdSchema,102 scopeId: ScopeIdSchema,
95 sourceId: SourceIdSchema,103 sourceId: SourceIdSchema,
96 catalogId: SourceCatalogIdSchema,104 catalogId: SourceCatalogIdSchema,
97 catalogRevisionId: SourceCatalogRevisionIdSchema,105 catalogRevisionId: SourceCatalogRevisionIdSchema,
@@ -113,7 +121,7 @@ export const StoredSourceRecordSchema = Schema.transform( · 15 unmodified lines
113 SourceStorageRowSchema,121 SourceStorageRowSchema,
114 Schema.Struct({122 Schema.Struct({
115 id: SourceIdSchema,123 id: SourceIdSchema,
116 workspaceId: WorkspaceIdSchema,124 scopeId: ScopeIdSchema,
117 catalogId: SourceCatalogIdSchema,125 catalogId: SourceCatalogIdSchema,
118 catalogRevisionId: SourceCatalogRevisionIdSchema,126 catalogRevisionId: SourceCatalogRevisionIdSchema,
119 name: Schema.String,127 name: Schema.String,
@@ -133,7 +141,7 @@ export const StoredSourceRecordSchema = Schema.transform( · 13 unmodified lines
133 strict: false,141 strict: false,
134 decode: (row) => ({142 decode: (row) => ({
135 id: row.sourceId,143 id: row.sourceId,
136 workspaceId: row.workspaceId,144 scopeId: row.scopeId,
137 catalogId: row.catalogId,145 catalogId: row.catalogId,
138 catalogRevisionId: row.catalogRevisionId,146 catalogRevisionId: row.catalogRevisionId,
139 name: row.name,147 name: row.name,
@@ -150,7 +158,7 @@ export const StoredSourceRecordSchema = Schema.transform( · 10 unmodified lines
150 updatedAt: row.updatedAt,158 updatedAt: row.updatedAt,
151 }),159 }),
152 encode: (source) => ({160 encode: (source) => ({
153 workspaceId: source.workspaceId,161 scopeId: source.scopeId,
154 sourceId: source.id,162 sourceId: source.id,
155 catalogId: source.catalogId,163 catalogId: source.catalogId,
156 catalogRevisionId: source.catalogRevisionId,164 catalogRevisionId: source.catalogRevisionId,
@@ -172,7 +180,7 @@ export const StoredSourceRecordSchema = Schema.transform( · 15 unmodified lines
172 180
173export const SourceSchema = Schema.Struct({181export const SourceSchema = Schema.Struct({
174 id: SourceIdSchema,182 id: SourceIdSchema,
175 workspaceId: WorkspaceIdSchema,183 scopeId: ScopeIdSchema,
176 name: Schema.String,184 name: Schema.String,
177 kind: SourceKindSchema,185 kind: SourceKindSchema,
178 endpoint: Schema.String,186 endpoint: Schema.String,
packages/platform/sdk/src/schema/schema.test.ts+5−1
@@ -1,4 +1,8 @@
1import { describe, expect, it } from "@effect/vitest";1import {
2 describe,
3 expect,
4 it,
5} from "@effect/vitest";
2import * as Either from "effect/Either";6import * as Either from "effect/Either";
3import * as Schema from "effect/Schema";7import * as Schema from "effect/Schema";
4 8
packages/platform/sdk/src/scope.ts+17−0
@@ -0,0 +1,17 @@
1import type {
2 ScopeId,
3} from "./schema";
4
5export type ExecutorScopeDescriptor = {
6 scopeName: string;
7 scopeRoot?: string | null;
8 actorScopeId?: ScopeId | null;
9 resolutionScopeIds?: ReadonlyArray<ScopeId>;
10 metadata?: Readonly<Record<string, unknown>>;
11};
12
13export type ExecutorScopeContext = ExecutorScopeDescriptor & {
14 scopeId: ScopeId;
15 actorScopeId: ScopeId | null;
16 resolutionScopeIds: ReadonlyArray<ScopeId>;
17};
packages/platform/sdk/src/sources/contracts.ts+11−11
@@ -12,9 +12,9 @@ import { · 11 unmodified lines
12 SourceSchema,12 SourceSchema,
13 SourceStatusSchema,13 SourceStatusSchema,
14 SourceOauthClientInputSchema,14 SourceOauthClientInputSchema,
15 WorkspaceIdSchema,15 ScopeIdSchema,
16 WorkspaceOauthClientIdSchema,16 ScopeOauthClientIdSchema,
17 WorkspaceOauthClientSchema,17 ScopeOauthClientSchema,
18} from "../schema";18} from "../schema";
19import {19import {
20 ConnectSourcePayloadSchema,20 ConnectSourcePayloadSchema,
@@ -84,20 +84,20 @@ export const CredentialOauthCompleteUrlParamsSchema = Schema.Struct({ · 63 unmodified lines
84 error_description: Schema.optional(Schema.String),84 error_description: Schema.optional(Schema.String),
85});85});
86 86
87export const WorkspaceOauthClientQuerySchema = Schema.Struct({87export const ScopeOauthClientQuerySchema = Schema.Struct({
88 providerKey: Schema.String,88 providerKey: Schema.String,
89});89});
90 90
91export const CreateWorkspaceOauthClientPayloadSchema = Schema.Struct({91export const CreateScopeOauthClientPayloadSchema = Schema.Struct({
92 providerKey: Schema.String,92 providerKey: Schema.String,
93 label: Schema.optional(Schema.NullOr(Schema.String)),93 label: Schema.optional(Schema.NullOr(Schema.String)),
94 oauthClient: SourceOauthClientInputSchema,94 oauthClient: SourceOauthClientInputSchema,
95});95});
96 96
97export type CreateWorkspaceOauthClientPayload =97export type CreateScopeOauthClientPayload =
98 typeof CreateWorkspaceOauthClientPayloadSchema.Type;98 typeof CreateScopeOauthClientPayloadSchema.Type;
99 99
100export const oauthClientIdParam = WorkspaceOauthClientIdSchema;100export const oauthClientIdParam = ScopeOauthClientIdSchema;
101export const grantIdParam = ProviderAuthGrantIdSchema;101export const grantIdParam = ProviderAuthGrantIdSchema;
102 102
103const ConnectGoogleDiscoveryBatchSourceSchema = Schema.Struct({103const ConnectGoogleDiscoveryBatchSourceSchema = Schema.Struct({
@@ -110,7 +110,7 @@ const ConnectGoogleDiscoveryBatchSourceSchema = Schema.Struct({ · 6 unmodified lines
110});110});
111 111
112export const ConnectSourceBatchPayloadSchema = Schema.Struct({112export const ConnectSourceBatchPayloadSchema = Schema.Struct({
113 workspaceOauthClientId: WorkspaceOauthClientIdSchema,113 scopeOauthClientId: ScopeOauthClientIdSchema,
114 sources: Schema.Array(ConnectGoogleDiscoveryBatchSourceSchema),114 sources: Schema.Array(ConnectGoogleDiscoveryBatchSourceSchema),
115});115});
116 116
@@ -165,6 +165,6 @@ export { · 48 unmodified lines
165 ConnectSourcePayloadSchema,165 ConnectSourcePayloadSchema,
166 type ConnectSourcePayload,166 type ConnectSourcePayload,
167 SourceDiscoveryResultSchema,167 SourceDiscoveryResultSchema,
168 WorkspaceIdSchema,168 ScopeIdSchema,
169 WorkspaceOauthClientSchema,169 ScopeOauthClientSchema,
170};170};
packages/platform/sdk/src/sources/discovery.ts+3−1
@@ -9,7 +9,9 @@ import { · 8 unmodified lines
9 type SourceDiscoveryResult,9 type SourceDiscoveryResult,
10} from "@executor/source-core";10} from "@executor/source-core";
11 11
12import { builtInSourceAdapters } from "../runtime/sources/source-adapters";12import {
13 builtInSourceAdapters,
14} from "../runtime/sources/source-adapters";
13 15
14type DiscoverableSourceAdapter = SourceAdapter & {16type DiscoverableSourceAdapter = SourceAdapter & {
15 detectSource: NonNullable<SourceAdapter["detectSource"]>;17 detectSource: NonNullable<SourceAdapter["detectSource"]>;
packages/platform/sdk/src/sources/inspection.ts+25−19
@@ -8,7 +8,7 @@ import type { · 7 unmodified lines
8 SourceInspectionToolDetail,8 SourceInspectionToolDetail,
9 SourceInspectionToolListItem,9 SourceInspectionToolListItem,
10 SourceInspectionToolSummary,10 SourceInspectionToolSummary,
11 WorkspaceId,11 ScopeId,
12} from "../schema";12} from "../schema";
13import {13import {
14 ControlPlaneNotFoundError,14 ControlPlaneNotFoundError,
@@ -16,8 +16,12 @@ import { · 1 unmodified line
16} from "../errors";16} from "../errors";
17import * as Effect from "effect/Effect";17import * as Effect from "effect/Effect";
18 18
19import { LocalSourceArtifactMissingError } from "../runtime/workspace-errors";19import {
20import { operationErrors } from "../runtime/policy/operation-errors";20 LocalSourceArtifactMissingError,
21} from "../runtime/scope-errors";
22import {
23 operationErrors,
24} from "../runtime/policy/operation-errors";
21import {25import {
22 buildLoadedSourceCatalogToolContract,26 buildLoadedSourceCatalogToolContract,
23 expandCatalogToolByPath,27 expandCatalogToolByPath,
@@ -25,7 +29,9 @@ import { · 1 unmodified line
25 loadSourceWithCatalog,29 loadSourceWithCatalog,
26 type LoadedSourceCatalogTool,30 type LoadedSourceCatalogTool,
27} from "../runtime/catalog/source/runtime";31} from "../runtime/catalog/source/runtime";
28import { RuntimeSourceStoreService } from "../runtime/sources/source-store";32import {
33 RuntimeSourceStoreService,
34} from "../runtime/sources/source-store";
29 35
30const sourceInspectOps = {36const sourceInspectOps = {
31 bundle: operationErrors("sources.inspect.bundle"),37 bundle: operationErrors("sources.inspect.bundle"),
@@ -46,14 +52,14 @@ const canInspectSourceWithoutCatalog = (source: Source): boolean => · 14 unmodified lines
46 source.status === "auth_required";52 source.status === "auth_required";
47 53
48const loadSourceForMissingCatalog = (input: {54const loadSourceForMissingCatalog = (input: {
49 workspaceId: WorkspaceId;55 scopeId: ScopeId;
50 sourceId: SourceId;56 sourceId: SourceId;
51 cause: LocalSourceArtifactMissingError;57 cause: LocalSourceArtifactMissingError;
52}) =>58}) =>
53 Effect.gen(function* () {59 Effect.gen(function* () {
54 const sourceStore = yield* RuntimeSourceStoreService;60 const sourceStore = yield* RuntimeSourceStoreService;
55 const source = yield* sourceStore.loadSourceById({61 const source = yield* sourceStore.loadSourceById({
56 workspaceId: input.workspaceId,62 scopeId: input.scopeId,
57 sourceId: input.sourceId,63 sourceId: input.sourceId,
58 });64 });
59 65
@@ -65,11 +71,11 @@ const loadSourceForMissingCatalog = (input: { · 5 unmodified lines
65 });71 });
66 72
67const loadSourceCatalogOrEmpty = (input: {73const loadSourceCatalogOrEmpty = (input: {
68 workspaceId: WorkspaceId;74 scopeId: ScopeId;
69 sourceId: SourceId;75 sourceId: SourceId;
70}) =>76}) =>
71 loadSourceWithCatalog({77 loadSourceWithCatalog({
72 workspaceId: input.workspaceId,78 scopeId: input.scopeId,
73 sourceId: input.sourceId,79 sourceId: input.sourceId,
74 }).pipe(80 }).pipe(
75 Effect.map((catalogEntry) => ({81 Effect.map((catalogEntry) => ({
@@ -79,7 +85,7 @@ const loadSourceCatalogOrEmpty = (input: { · 3 unmodified lines
79 Effect.catchTag("LocalSourceArtifactMissingError", (cause) =>85 Effect.catchTag("LocalSourceArtifactMissingError", (cause) =>
80 Effect.gen(function* () {86 Effect.gen(function* () {
81 const source = yield* loadSourceForMissingCatalog({87 const source = yield* loadSourceForMissingCatalog({
82 workspaceId: input.workspaceId,88 scopeId: input.scopeId,
83 sourceId: input.sourceId,89 sourceId: input.sourceId,
84 cause,90 cause,
85 });91 });
@@ -273,14 +279,14 @@ export const inspectionToolDetailFromTool = ( · 187 unmodified lines
273 });279 });
274 280
275const resolveSourceInspection = (input: {281const resolveSourceInspection = (input: {
276 workspaceId: WorkspaceId;282 scopeId: ScopeId;
277 sourceId: SourceId;283 sourceId: SourceId;
278 includeSchemas: boolean;284 includeSchemas: boolean;
279 includeTypePreviews: boolean;285 includeTypePreviews: boolean;
280}) =>286}) =>
281 Effect.gen(function* () {287 Effect.gen(function* () {
282 const loaded = yield* loadSourceCatalogOrEmpty({288 const loaded = yield* loadSourceCatalogOrEmpty({
283 workspaceId: input.workspaceId,289 scopeId: input.scopeId,
284 sourceId: input.sourceId,290 sourceId: input.sourceId,
285 });291 });
286 292
@@ -308,13 +314,13 @@ const resolveSourceInspection = (input: { · 21 unmodified lines
308 });314 });
309 315
310const resolveSourceInspectionTool = (input: {316const resolveSourceInspectionTool = (input: {
311 workspaceId: WorkspaceId;317 scopeId: ScopeId;
312 sourceId: SourceId;318 sourceId: SourceId;
313 toolPath: string;319 toolPath: string;
314}) =>320}) =>
315 Effect.gen(function* () {321 Effect.gen(function* () {
316 const loaded = yield* loadSourceCatalogOrEmpty({322 const loaded = yield* loadSourceCatalogOrEmpty({
317 workspaceId: input.workspaceId,323 scopeId: input.scopeId,
318 sourceId: input.sourceId,324 sourceId: input.sourceId,
319 });325 });
320 326
@@ -425,7 +431,7 @@ const mapInspectionError = ( · 104 unmodified lines
425};431};
426 432
427export const getSourceInspection = (input: {433export const getSourceInspection = (input: {
428 workspaceId: WorkspaceId;434 scopeId: ScopeId;
429 sourceId: SourceId;435 sourceId: SourceId;
430}) =>436}) =>
431 Effect.gen(function* () {437 Effect.gen(function* () {
@@ -456,13 +462,13 @@ export const getSourceInspection = (input: { · 24 unmodified lines
456 );462 );
457 463
458export const getSourceInspectionToolDetail = (input: {464export const getSourceInspectionToolDetail = (input: {
459 workspaceId: WorkspaceId;465 scopeId: ScopeId;
460 sourceId: SourceId;466 sourceId: SourceId;
461 toolPath: string;467 toolPath: string;
462}) =>468}) =>
463 Effect.gen(function* () {469 Effect.gen(function* () {
464 const inspection = yield* resolveSourceInspectionTool({470 const inspection = yield* resolveSourceInspectionTool({
465 workspaceId: input.workspaceId,471 scopeId: input.scopeId,
466 sourceId: input.sourceId,472 sourceId: input.sourceId,
467 toolPath: input.toolPath,473 toolPath: input.toolPath,
468 });474 });
@@ -471,7 +477,7 @@ export const getSourceInspectionToolDetail = (input: { · 2 unmodified lines
471 if (!tool) {477 if (!tool) {
472 return yield* sourceInspectOps.tool.notFound(478 return yield* sourceInspectOps.tool.notFound(
473 "Tool not found",479 "Tool not found",
474 `workspaceId=${input.workspaceId} sourceId=${input.sourceId} path=${input.toolPath}`,480 `scopeId=${input.scopeId} sourceId=${input.sourceId} path=${input.toolPath}`,
475 );481 );
476 }482 }
477 483
@@ -487,13 +493,13 @@ export const getSourceInspectionToolDetail = (input: { · 9 unmodified lines
487 );493 );
488 494
489export const discoverSourceInspectionTools = (input: {495export const discoverSourceInspectionTools = (input: {
490 workspaceId: WorkspaceId;496 scopeId: ScopeId;
491 sourceId: SourceId;497 sourceId: SourceId;
492 payload: SourceInspectionDiscoverPayload;498 payload: SourceInspectionDiscoverPayload;
493}) =>499}) =>
494 Effect.gen(function* () {500 Effect.gen(function* () {
495 const inspection = yield* resolveSourceInspection({501 const inspection = yield* resolveSourceInspection({
496 workspaceId: input.workspaceId,502 scopeId: input.scopeId,
497 sourceId: input.sourceId,503 sourceId: input.sourceId,
498 includeSchemas: false,504 includeSchemas: false,
499 includeTypePreviews: false,505 includeTypePreviews: false,
packages/platform/sdk/src/sources/operations.ts+43−34
@@ -3,11 +3,10 @@ import type { · 2 unmodified lines
3 UpdateSourcePayload,3 UpdateSourcePayload,
4} from "./contracts";4} from "./contracts";
5import {5import {
6 type AccountId,6 type ScopeId,
7 SourceIdSchema,7 SourceIdSchema,
8 type Source,8 type Source,
9 type SourceId,9 type SourceId,
10 type WorkspaceId,
11} from "../schema";10} from "../schema";
12import * as Either from "effect/Either";11import * as Either from "effect/Either";
13import * as Effect from "effect/Effect";12import * as Effect from "effect/Effect";
@@ -16,15 +15,25 @@ import { · 2 unmodified lines
16 createSourceFromPayload,15 createSourceFromPayload,
17 updateSourceFromPayload,16 updateSourceFromPayload,
18} from "../runtime/sources/source-definitions";17} from "../runtime/sources/source-definitions";
19import { getSourceAdapterForSource } from "../runtime/sources/source-adapters";18import {
20import { mapPersistenceError } from "../runtime/policy/operations-shared";19 getSourceAdapterForSource,
21import { operationErrors } from "../runtime/policy/operation-errors";20} from "../runtime/sources/source-adapters";
21import {
22 mapPersistenceError,
23} from "../runtime/policy/operations-shared";
24import {
25 operationErrors,
26} from "../runtime/policy/operation-errors";
22import {27import {
23 ExecutorStateStore,28 ExecutorStateStore,
24 type ExecutorStateStoreShape,29 type ExecutorStateStoreShape,
25} from "../runtime/executor-state-store";30} from "../runtime/executor-state-store";
26import { RuntimeSourceCatalogSyncService } from "../runtime/catalog/source/sync";31import {
27import { RuntimeSourceStoreService } from "../runtime/sources/source-store";32 RuntimeSourceCatalogSyncService,
33} from "../runtime/catalog/source/sync";
34import {
35 RuntimeSourceStoreService,
36} from "../runtime/sources/source-store";
28 37
29const sourceOps = {38const sourceOps = {
30 list: operationErrors("sources.list"),39 list: operationErrors("sources.list"),
@@ -41,7 +50,7 @@ const syncArtifactsForSource = (input: { · 10 unmodified lines
41 store: ExecutorStateStoreShape;50 store: ExecutorStateStoreShape;
42 sourceStore: Effect.Effect.Success<typeof RuntimeSourceStoreService>;51 sourceStore: Effect.Effect.Success<typeof RuntimeSourceStoreService>;
43 source: Source;52 source: Source;
44 actorAccountId: AccountId;53 actorScopeId: ScopeId;
45 operation: typeof sourceOps.create | typeof sourceOps.update;54 operation: typeof sourceOps.create | typeof sourceOps.update;
46}) =>55}) =>
47 Effect.gen(function* () {56 Effect.gen(function* () {
@@ -59,7 +68,7 @@ const syncArtifactsForSource = (input: { · 11 unmodified lines
59 const synced = yield* Effect.either(68 const synced = yield* Effect.either(
60 catalogSyncService.sync({69 catalogSyncService.sync({
61 source: sourceForSync,70 source: sourceForSync,
62 actorAccountId: input.actorAccountId,71 actorScopeId: input.actorScopeId,
63 }),72 }),
64 );73 );
65 74
@@ -82,7 +91,7 @@ const syncArtifactsForSource = (input: { · 16 unmodified lines
82 yield* mapPersistenceError(91 yield* mapPersistenceError(
83 input.operation.child("source_connected"),92 input.operation.child("source_connected"),
84 input.sourceStore.persistSource(connectedSource, {93 input.sourceStore.persistSource(connectedSource, {
85 actorAccountId: input.actorAccountId,94 actorScopeId: input.actorScopeId,
86 }),95 }),
87 );96 );
88 return connectedSource;97 return connectedSource;
@@ -114,7 +123,7 @@ const syncArtifactsForSource = (input: { · 25 unmodified lines
114 yield* mapPersistenceError(123 yield* mapPersistenceError(
115 input.operation.child("source_error"),124 input.operation.child("source_error"),
116 input.sourceStore.persistSource(erroredSource, {125 input.sourceStore.persistSource(erroredSource, {
117 actorAccountId: input.actorAccountId,126 actorScopeId: input.actorScopeId,
118 }),127 }),
119 );128 );
120 }129 }
@@ -128,16 +137,16 @@ const syncArtifactsForSource = (input: { · 7 unmodified lines
128 });137 });
129 138
130export const listSources = (input: {139export const listSources = (input: {
131 workspaceId: WorkspaceId;140 scopeId: ScopeId;
132 accountId: AccountId;141 actorScopeId: ScopeId;
133}) =>142}) =>
134 Effect.flatMap(ExecutorStateStore, () =>143 Effect.flatMap(ExecutorStateStore, () =>
135 Effect.gen(function* () {144 Effect.gen(function* () {
136 const sourceStore = yield* RuntimeSourceStoreService;145 const sourceStore = yield* RuntimeSourceStoreService;
137 146
138 return yield* sourceStore147 return yield* sourceStore
139 .loadSourcesInWorkspace(input.workspaceId, {148 .loadSourcesInScope(input.scopeId, {
140 actorAccountId: input.accountId,149 actorScopeId: input.actorScopeId,
141 })150 })
142 .pipe(151 .pipe(
143 Effect.mapError((error) =>152 Effect.mapError((error) =>
@@ -151,8 +160,8 @@ export const listSources = (input: { · 7 unmodified lines
151 );160 );
152 161
153export const createSource = (input: {162export const createSource = (input: {
154 workspaceId: WorkspaceId;163 scopeId: ScopeId;
155 accountId: AccountId;164 actorScopeId: ScopeId;
156 payload: CreateSourcePayload;165 payload: CreateSourcePayload;
157}) =>166}) =>
158 Effect.flatMap(ExecutorStateStore, (store) =>167 Effect.flatMap(ExecutorStateStore, (store) =>
@@ -161,7 +170,7 @@ export const createSource = (input: { · 2 unmodified lines
161 const now = Date.now();170 const now = Date.now();
162 171
163 const source = yield* createSourceFromPayload({172 const source = yield* createSourceFromPayload({
164 workspaceId: input.workspaceId,173 scopeId: input.scopeId,
165 sourceId: SourceIdSchema.make(`src_${crypto.randomUUID()}`),174 sourceId: SourceIdSchema.make(`src_${crypto.randomUUID()}`),
166 payload: input.payload,175 payload: input.payload,
167 now,176 now,
@@ -177,7 +186,7 @@ export const createSource = (input: { · 9 unmodified lines
177 const persistedSource = yield* mapPersistenceError(186 const persistedSource = yield* mapPersistenceError(
178 sourceOps.create.child("persist"),187 sourceOps.create.child("persist"),
179 sourceStore.persistSource(source, {188 sourceStore.persistSource(source, {
180 actorAccountId: input.accountId,189 actorScopeId: input.actorScopeId,
181 }),190 }),
182 );191 );
183 192
@@ -185,7 +194,7 @@ export const createSource = (input: { · 1 unmodified line
185 store,194 store,
186 sourceStore,195 sourceStore,
187 source: persistedSource,196 source: persistedSource,
188 actorAccountId: input.accountId,197 actorScopeId: input.actorScopeId,
189 operation: sourceOps.create,198 operation: sourceOps.create,
190 });199 });
191 200
@@ -194,9 +203,9 @@ export const createSource = (input: { · 2 unmodified lines
194 );203 );
195 204
196export const getSource = (input: {205export const getSource = (input: {
197 workspaceId: WorkspaceId;206 scopeId: ScopeId;
198 sourceId: SourceId;207 sourceId: SourceId;
199 accountId: AccountId;208 actorScopeId: ScopeId;
200}) =>209}) =>
201 Effect.flatMap(ExecutorStateStore, () =>210 Effect.flatMap(ExecutorStateStore, () =>
202 Effect.gen(function* () {211 Effect.gen(function* () {
@@ -204,9 +213,9 @@ export const getSource = (input: { · 1 unmodified line
204 213
205 return yield* sourceStore214 return yield* sourceStore
206 .loadSourceById({215 .loadSourceById({
207 workspaceId: input.workspaceId,216 scopeId: input.scopeId,
208 sourceId: input.sourceId,217 sourceId: input.sourceId,
209 actorAccountId: input.accountId,218 actorScopeId: input.actorScopeId,
210 })219 })
211 .pipe(220 .pipe(
212 Effect.mapError((cause) =>221 Effect.mapError((cause) =>
@@ -214,7 +223,7 @@ export const getSource = (input: { · 1 unmodified line
214 cause.message.startsWith("Source not found:")223 cause.message.startsWith("Source not found:")
215 ? sourceOps.get.notFound(224 ? sourceOps.get.notFound(
216 "Source not found",225 "Source not found",
217 `workspaceId=${input.workspaceId} sourceId=${input.sourceId}`,226 `scopeId=${input.scopeId} sourceId=${input.sourceId}`,
218 )227 )
219 : sourceOps.get.unknownStorage(228 : sourceOps.get.unknownStorage(
220 cause,229 cause,
@@ -226,9 +235,9 @@ export const getSource = (input: { · 5 unmodified lines
226 );235 );
227 236
228export const updateSource = (input: {237export const updateSource = (input: {
229 workspaceId: WorkspaceId;238 scopeId: ScopeId;
230 sourceId: SourceId;239 sourceId: SourceId;
231 accountId: AccountId;240 actorScopeId: ScopeId;
232 payload: UpdateSourcePayload;241 payload: UpdateSourcePayload;
233}) =>242}) =>
234 Effect.flatMap(ExecutorStateStore, (store) =>243 Effect.flatMap(ExecutorStateStore, (store) =>
@@ -236,9 +245,9 @@ export const updateSource = (input: { · 1 unmodified line
236 const sourceStore = yield* RuntimeSourceStoreService;245 const sourceStore = yield* RuntimeSourceStoreService;
237 const existingSource = yield* sourceStore246 const existingSource = yield* sourceStore
238 .loadSourceById({247 .loadSourceById({
239 workspaceId: input.workspaceId,248 scopeId: input.scopeId,
240 sourceId: input.sourceId,249 sourceId: input.sourceId,
241 actorAccountId: input.accountId,250 actorScopeId: input.actorScopeId,
242 })251 })
243 .pipe(252 .pipe(
244 Effect.mapError((cause) =>253 Effect.mapError((cause) =>
@@ -246,7 +255,7 @@ export const updateSource = (input: { · 1 unmodified line
246 cause.message.startsWith("Source not found:")255 cause.message.startsWith("Source not found:")
247 ? sourceOps.update.notFound(256 ? sourceOps.update.notFound(
248 "Source not found",257 "Source not found",
249 `workspaceId=${input.workspaceId} sourceId=${input.sourceId}`,258 `scopeId=${input.scopeId} sourceId=${input.sourceId}`,
250 )259 )
251 : sourceOps.update.unknownStorage(260 : sourceOps.update.unknownStorage(
252 cause,261 cause,
@@ -271,7 +280,7 @@ export const updateSource = (input: { · 18 unmodified lines
271 const persistedSource = yield* mapPersistenceError(280 const persistedSource = yield* mapPersistenceError(
272 sourceOps.update.child("persist"),281 sourceOps.update.child("persist"),
273 sourceStore.persistSource(updatedSource, {282 sourceStore.persistSource(updatedSource, {
274 actorAccountId: input.accountId,283 actorScopeId: input.actorScopeId,
275 }),284 }),
276 );285 );
277 286
@@ -279,7 +288,7 @@ export const updateSource = (input: { · 1 unmodified line
279 store,288 store,
280 sourceStore,289 sourceStore,
281 source: persistedSource,290 source: persistedSource,
282 actorAccountId: input.accountId,291 actorScopeId: input.actorScopeId,
283 operation: sourceOps.update,292 operation: sourceOps.update,
284 });293 });
285 294
@@ -288,7 +297,7 @@ export const updateSource = (input: { · 2 unmodified lines
288 );297 );
289 298
290export const removeSource = (input: {299export const removeSource = (input: {
291 workspaceId: WorkspaceId;300 scopeId: ScopeId;
292 sourceId: SourceId;301 sourceId: SourceId;
293}) =>302}) =>
294 Effect.flatMap(ExecutorStateStore, () =>303 Effect.flatMap(ExecutorStateStore, () =>
@@ -297,7 +306,7 @@ export const removeSource = (input: { · 2 unmodified lines
297 const removed = yield* mapPersistenceError(306 const removed = yield* mapPersistenceError(
298 sourceOps.remove.child("remove"),307 sourceOps.remove.child("remove"),
299 sourceStore.removeSourceById({308 sourceStore.removeSourceById({
300 workspaceId: input.workspaceId,309 scopeId: input.scopeId,
301 sourceId: input.sourceId,310 sourceId: input.sourceId,
302 }),311 }),
303 );312 );
packages/platform/sdk/src/workspace.ts+0−15
@@ -1,15 +0,0 @@
1import type {
2 AccountId,
3 WorkspaceId,
4} from "./schema";
5
6export type ExecutorWorkspaceDescriptor = {
7 workspaceName: string;
8 workspaceRoot?: string | null;
9 metadata?: Readonly<Record<string, unknown>>;
10};
11
12export type ExecutorWorkspaceContext = ExecutorWorkspaceDescriptor & {
13 workspaceId: WorkspaceId;
14 accountId: AccountId;
15};
packages/sources/core/src/source-models.ts+6−6
@@ -134,7 +134,7 @@ export const SourceBindingSchema = Schema.Struct({ · 133 unmodified lines
134 134
135export const SourceSchema = Schema.Struct({135export const SourceSchema = Schema.Struct({
136 id: Schema.String,136 id: Schema.String,
137 workspaceId: Schema.String,137 scopeId: Schema.String,
138 name: Schema.String,138 name: Schema.String,
139 kind: SourceKindSchema,139 kind: SourceKindSchema,
140 endpoint: Schema.String,140 endpoint: Schema.String,
@@ -157,19 +157,19 @@ export const StoredSourceRecordSchema = Schema.Struct({ · 16 unmodified lines
157 bindingConfigJson: Schema.NullOr(Schema.String),157 bindingConfigJson: Schema.NullOr(Schema.String),
158});158});
159 159
160export const WorkspaceSourceOauthClientRedirectModeSchema = Schema.Literal(160export const ScopedSourceOauthClientRedirectModeSchema = Schema.Literal(
161 "app_callback",161 "app_callback",
162 "loopback",162 "loopback",
163);163);
164 164
165export const WorkspaceOauthClientIdSchema = Schema.String;165export const ScopeOauthClientIdSchema = Schema.String;
166 166
167export const SourceOauthClientInputSchema = Schema.Struct({167export const SourceOauthClientInputSchema = Schema.Struct({
168 clientId: Schema.Trim.pipe(Schema.nonEmptyString()),168 clientId: Schema.Trim.pipe(Schema.nonEmptyString()),
169 clientSecret: Schema.optional(169 clientSecret: Schema.optional(
170 Schema.NullOr(Schema.Trim.pipe(Schema.nonEmptyString())),170 Schema.NullOr(Schema.Trim.pipe(Schema.nonEmptyString())),
171 ),171 ),
172 redirectMode: Schema.optional(WorkspaceSourceOauthClientRedirectModeSchema),172 redirectMode: Schema.optional(ScopedSourceOauthClientRedirectModeSchema),
173});173});
174 174
175export type SecretRef = typeof SecretRefSchema.Type;175export type SecretRef = typeof SecretRefSchema.Type;
@@ -185,6 +185,6 @@ export type SourceAuth = typeof SourceAuthSchema.Type; · 9 unmodified lines
185export type SourceBinding = typeof SourceBindingSchema.Type;185export type SourceBinding = typeof SourceBindingSchema.Type;
186export type Source = typeof SourceSchema.Type;186export type Source = typeof SourceSchema.Type;
187export type StoredSourceRecord = typeof StoredSourceRecordSchema.Type;187export type StoredSourceRecord = typeof StoredSourceRecordSchema.Type;
188export type WorkspaceSourceOauthClientRedirectMode =188export type ScopedSourceOauthClientRedirectMode =
189 typeof WorkspaceSourceOauthClientRedirectModeSchema.Type;189 typeof ScopedSourceOauthClientRedirectModeSchema.Type;
190export type SourceOauthClientInput = typeof SourceOauthClientInputSchema.Type;190export type SourceOauthClientInput = typeof SourceOauthClientInputSchema.Type;
packages/sources/google-discovery/src/adapter.test.ts+1−1
@@ -81,7 +81,7 @@ const withJsonServer = async <T>(handler: (input: { · 80 unmodified lines
81 81
82const makeGoogleDiscoverySource = (discoveryUrl: string): Source => ({82const makeGoogleDiscoverySource = (discoveryUrl: string): Source => ({
83 id: "src_google_gmail",83 id: "src_google_gmail",
84 workspaceId: "ws_test",84 scopeId: "ws_test",
85 name: "Gmail",85 name: "Gmail",
86 kind: "google_discovery",86 kind: "google_discovery",
87 endpoint: discoveryUrl,87 endpoint: discoveryUrl,
packages/sources/google-discovery/src/adapter.ts+3−3
@@ -38,7 +38,7 @@ import { · 37 unmodified lines
38 OptionalNullableStringSchema,38 OptionalNullableStringSchema,
39 SourceCredentialRequiredError,39 SourceCredentialRequiredError,
40 StringMapSchema,40 StringMapSchema,
41 WorkspaceOauthClientIdSchema,41 ScopeOauthClientIdSchema,
42 createCatalogImportMetadata,42 createCatalogImportMetadata,
43 EXECUTABLE_BINDING_VERSION,43 EXECUTABLE_BINDING_VERSION,
44 sourceCoreEffectError,44 sourceCoreEffectError,
@@ -60,8 +60,8 @@ const GoogleDiscoveryConnectPayloadSchema = Schema.extend( · 15 unmodified lines
60 scopes: Schema.optional(60 scopes: Schema.optional(
61 Schema.Array(Schema.Trim.pipe(Schema.nonEmptyString())),61 Schema.Array(Schema.Trim.pipe(Schema.nonEmptyString())),
62 ),62 ),
63 workspaceOauthClientId: Schema.optional(63 scopeOauthClientId: Schema.optional(
64 Schema.NullOr(WorkspaceOauthClientIdSchema),64 Schema.NullOr(ScopeOauthClientIdSchema),
65 ),65 ),
66 oauthClient: ConnectOauthClientSchema,66 oauthClient: ConnectOauthClientSchema,
67 name: OptionalNullableStringSchema,67 name: OptionalNullableStringSchema,
tools/oxlint/README.md+2−0
@@ -14,6 +14,8 @@ Current rules: · 13 unmodified lines
14- `executor-monorepo/no-cross-workspace-relative-imports`14- `executor-monorepo/no-cross-workspace-relative-imports`
15- `executor-monorepo/no-direct-effect-tag-read`15- `executor-monorepo/no-direct-effect-tag-read`
16- `executor-monorepo/no-effect-run-in-effect-vitest-tests`16- `executor-monorepo/no-effect-run-in-effect-vitest-tests`
17- `executor-monorepo/no-manual-httpapi-openapi-bridge`
18- `executor-monorepo/no-manual-openapi-documents`
17- `executor-monorepo/no-node-fs-with-effect-imports`19- `executor-monorepo/no-node-fs-with-effect-imports`
18- `executor-monorepo/no-raw-effect-fail-errors`20- `executor-monorepo/no-raw-effect-fail-errors`
19- `executor-monorepo/no-yield-effect-fail`21- `executor-monorepo/no-yield-effect-fail`
tools/oxlint/plugin.mjs+4−0
@@ -4,6 +4,8 @@ import noCrossWorkspaceRelativeImports from "./rules/no-cross-workspace-relative · 3 unmodified lines
4import noAsyncEffectVitestTests from "./rules/no-async-effect-vitest-tests.mjs";4import noAsyncEffectVitestTests from "./rules/no-async-effect-vitest-tests.mjs";
5import noDirectEffectTagRead from "./rules/no-direct-effect-tag-read.mjs";5import noDirectEffectTagRead from "./rules/no-direct-effect-tag-read.mjs";
6import noEffectRunInEffectVitestTests from "./rules/no-effect-run-in-effect-vitest-tests.mjs";6import noEffectRunInEffectVitestTests from "./rules/no-effect-run-in-effect-vitest-tests.mjs";
7import noManualHttpApiOpenApiBridge from "./rules/no-manual-httpapi-openapi-bridge.mjs";
8import noManualOpenApiDocuments from "./rules/no-manual-openapi-documents.mjs";
7import noNodeFsWithEffectImports from "./rules/no-node-fs-with-effect-imports.mjs";9import noNodeFsWithEffectImports from "./rules/no-node-fs-with-effect-imports.mjs";
8import noRawEffectFailErrors from "./rules/no-raw-effect-fail-errors.mjs";10import noRawEffectFailErrors from "./rules/no-raw-effect-fail-errors.mjs";
9import noYieldEffectFail from "./rules/no-yield-effect-fail.mjs";11import noYieldEffectFail from "./rules/no-yield-effect-fail.mjs";
@@ -18,6 +20,8 @@ export default definePlugin({ · 8 unmodified lines
18 "no-cross-workspace-relative-imports": noCrossWorkspaceRelativeImports,20 "no-cross-workspace-relative-imports": noCrossWorkspaceRelativeImports,
19 "no-direct-effect-tag-read": noDirectEffectTagRead,21 "no-direct-effect-tag-read": noDirectEffectTagRead,
20 "no-effect-run-in-effect-vitest-tests": noEffectRunInEffectVitestTests,22 "no-effect-run-in-effect-vitest-tests": noEffectRunInEffectVitestTests,
23 "no-manual-httpapi-openapi-bridge": noManualHttpApiOpenApiBridge,
24 "no-manual-openapi-documents": noManualOpenApiDocuments,
21 "no-node-fs-with-effect-imports": noNodeFsWithEffectImports,25 "no-node-fs-with-effect-imports": noNodeFsWithEffectImports,
22 "no-raw-effect-fail-errors": noRawEffectFailErrors,26 "no-raw-effect-fail-errors": noRawEffectFailErrors,
23 "no-yield-effect-fail": noYieldEffectFail,27 "no-yield-effect-fail": noYieldEffectFail,
tools/oxlint/rules/no-manual-httpapi-openapi-bridge.mjs+65−0
@@ -0,0 +1,65 @@
1import { readFileSync } from "node:fs";
2import { defineRule } from "@oxlint/plugins";
3
4const isTestLikeFile = (filename) =>
5 filename.includes(".test.")
6 || filename.includes(".spec.")
7 || filename.endsWith(".test.ts")
8 || filename.endsWith(".test.tsx")
9 || filename.endsWith(".spec.ts")
10 || filename.endsWith(".spec.tsx");
11
12const readsLikeOpenApiHttpApiTest = (filename) => {
13 try {
14 const sourceText = readFileSync(filename, "utf8");
15 return sourceText.includes("HttpApiBuilder.middlewareOpenApi(")
16 || sourceText.includes("OpenApi.fromApi(");
17 } catch {
18 return false;
19 }
20};
21
22const isHttpApiBuilderCall = (node, methodName) =>
23 node?.type === "CallExpression"
24 && node.callee?.type === "MemberExpression"
25 && node.callee.computed === false
26 && node.callee.object?.type === "Identifier"
27 && node.callee.object.name === "HttpApiBuilder"
28 && node.callee.property?.type === "Identifier"
29 && node.callee.property.name === methodName;
30
31export default defineRule({
32 meta: {
33 type: "problem",
34 docs: {
35 description:
36 "Disallow manual Node/Web bridges around Effect HttpApi when serving OpenAPI test servers.",
37 recommended: true,
38 },
39 messages: {
40 noManualHttpApiOpenApiBridge:
41 "Do not serve OpenAPI test servers by manually bridging `HttpApiBuilder.toWebHandler(...)` to Node HTTP. Use `@executor/effect-test-utils` (`startOpenApiTestServer` / `makeOpenApiTestServer`) instead.",
42 },
43 },
44 create(context) {
45 if (
46 !isTestLikeFile(context.filename)
47 || !readsLikeOpenApiHttpApiTest(context.filename)
48 ) {
49 return {};
50 }
51
52 return {
53 CallExpression(node) {
54 if (!isHttpApiBuilderCall(node, "toWebHandler")) {
55 return;
56 }
57
58 context.report({
59 node,
60 messageId: "noManualHttpApiOpenApiBridge",
61 });
62 },
63 };
64 },
65});
tools/oxlint/rules/no-manual-openapi-documents.mjs+108−0
@@ -0,0 +1,108 @@
1import path from "node:path";
2import { defineRule } from "@oxlint/plugins";
3
4const MANUAL_OPENAPI_RULE_ALLOWED_PATH_SEGMENTS = [
5 `${path.sep}packages${path.sep}sources${path.sep}openapi${path.sep}`,
6 `${path.sep}packages${path.sep}platform${path.sep}sdk${path.sep}src${path.sep}runtime${path.sep}sources${path.sep}source-adapter-fixture-matrix.test.ts`,
7 `${path.sep}packages${path.sep}platform${path.sep}sdk${path.sep}src${path.sep}runtime${path.sep}sources${path.sep}source-discovery.test.ts`,
8];
9
10const OPENAPI_MARKER_KEYS = new Set(["openapi", "swagger"]);
11const OPENAPI_SHAPE_KEYS = new Set([
12 "components",
13 "info",
14 "paths",
15 "responses",
16 "security",
17 "servers",
18 "tags",
19]);
20
21const isTestLikeFile = (filename) =>
22 filename.includes(".test.")
23 || filename.includes(".spec.")
24 || filename.endsWith(".test.ts")
25 || filename.endsWith(".test.tsx")
26 || filename.endsWith(".spec.ts")
27 || filename.endsWith(".spec.tsx");
28
29const isAllowedFile = (filename) =>
30 MANUAL_OPENAPI_RULE_ALLOWED_PATH_SEGMENTS.some((segment) =>
31 filename.includes(segment)
32 );
33
34const readPropertyKey = (node) => {
35 if (!node || node.type !== "Property" || node.computed) {
36 return null;
37 }
38
39 const key = node.key;
40 if (!key) {
41 return null;
42 }
43
44 if (key.type === "Identifier") {
45 return key.name;
46 }
47
48 if (key.type === "Literal" && typeof key.value === "string") {
49 return key.value;
50 }
51
52 return null;
53};
54
55const objectExpressionKeys = (node) => {
56 if (!node || node.type !== "ObjectExpression") {
57 return [];
58 }
59
60 return node.properties
61 .map(readPropertyKey)
62 .filter((key) => typeof key === "string");
63};
64
65const looksLikeOpenApiDocument = (node) => {
66 if (!node || node.type !== "ObjectExpression") {
67 return false;
68 }
69
70 const keys = new Set(objectExpressionKeys(node));
71 const hasOpenApiMarker = [...OPENAPI_MARKER_KEYS].some((key) => keys.has(key));
72 const hasOpenApiShape = [...OPENAPI_SHAPE_KEYS].some((key) => keys.has(key));
73
74 return hasOpenApiMarker && hasOpenApiShape;
75};
76
77export default defineRule({
78 meta: {
79 type: "problem",
80 docs: {
81 description:
82 "Disallow hand-authored inline OpenAPI documents in normal test files.",
83 recommended: true,
84 },
85 messages: {
86 noManualOpenApiDocuments:
87 "Do not hand-author inline OpenAPI documents here. Use Effect's HttpApi/OpenApi builders, such as `OpenApi.fromApi(...)`, instead. Raw OpenAPI fixtures should live only in dedicated OpenAPI parser/import tests.",
88 },
89 },
90 create(context) {
91 if (!isTestLikeFile(context.filename) || isAllowedFile(context.filename)) {
92 return {};
93 }
94
95 return {
96 ObjectExpression(node) {
97 if (!looksLikeOpenApiDocument(node)) {
98 return;
99 }
100
101 context.report({
102 node,
103 messageId: "noManualOpenApiDocuments",
104 });
105 },
106 };
107 },
108});