diff --git a/apps/server/src/auth/RpcAuthorization.ts b/apps/server/src/auth/RpcAuthorization.ts index 174a5e8b29cee..ae8522133afa3 100644 --- a/apps/server/src/auth/RpcAuthorization.ts +++ b/apps/server/src/auth/RpcAuthorization.ts @@ -14,7 +14,6 @@ import { AuthProvidersManageScope, AuthEnvironmentMaintainScope, AuthFilesystemReadScope, - AuthFilesystemWriteScope, AuthDiagnosticsReadScope, AuthOrchestrationOperateScope, AuthOrchestrationReadScope, @@ -151,7 +150,6 @@ export const RPC_REQUIRED_SCOPES = { [WS_METHODS.projectsReadFile]: AuthFilesystemReadScope, [WS_METHODS.projectsSearchContents]: AuthFilesystemReadScope, [WS_METHODS.projectsSearchEntries]: AuthFilesystemReadScope, - [WS_METHODS.projectsWriteFile]: AuthFilesystemWriteScope, [WS_METHODS.projectsEnsureScratch]: AuthOrchestrationOperateScope, [WS_METHODS.projectsCreateNew]: AuthOrchestrationOperateScope, [WS_METHODS.shellOpenInEditor]: AuthOrchestrationOperateScope, diff --git a/packages/client-runtime/src/state/commandPermissions.test.ts b/packages/client-runtime/src/state/commandPermissions.test.ts index 79276f3851504..d88d63761e84e 100644 --- a/packages/client-runtime/src/state/commandPermissions.test.ts +++ b/packages/client-runtime/src/state/commandPermissions.test.ts @@ -9,6 +9,7 @@ import { vi } from "vite-plus/test"; import { AuthAccessReadScope, AuthAccessWriteScope, + AuthFilesystemWriteScope, AuthOrchestrationOperateScope, AuthOrchestrationReadScope, AuthPreviewOperateScope, @@ -131,6 +132,27 @@ describe("command permissions", () => { ), ); + it.effect("requires the destination filesystem write grant to write a file", () => + Effect.scoped( + Effect.gen(function* () { + const registry = yield* setup; + const write = createCommandPermissions(runtime, WS_METHODS.projectsWriteFile); + registry.set(sessions(env), AsyncResult.success(grant(true))); + expect(registry.get(write.permissionAtom(env))).toBe(false); + registry.set( + sessions(env), + AsyncResult.success({ + ...grant(false), + scopes: [AuthFilesystemWriteScope], + permissions: [AuthFilesystemWriteScope], + }), + ); + expect(registry.get(write.permissionAtom(env))).toBe(true); + yield* write.authorize(registry, env); + }), + ), + ); + it.effect("requires the destination provider grant to remove an incomplete connection", () => Effect.scoped( Effect.gen(function* () { diff --git a/packages/client-runtime/src/state/projectCommands.test.ts b/packages/client-runtime/src/state/projectCommands.test.ts index 34c523f64e519..177840b12b2fe 100644 --- a/packages/client-runtime/src/state/projectCommands.test.ts +++ b/packages/client-runtime/src/state/projectCommands.test.ts @@ -125,6 +125,7 @@ const makeHarness = Effect.fn("TestProjectCommands.makeHarness")(function* () { }); const registry = AtomRegistry.make(); yield* Effect.addFinalizer(() => Effect.sync(() => registry.dispose())); + registry.mount(sessions(ENVIRONMENT_ID)); registry.set( sessions(ENVIRONMENT_ID), AsyncResult.success({ diff --git a/packages/contracts/src/clientRpcPermissions.ts b/packages/contracts/src/clientRpcPermissions.ts index caeee25295c6f..bbffc5175e211 100644 --- a/packages/contracts/src/clientRpcPermissions.ts +++ b/packages/contracts/src/clientRpcPermissions.ts @@ -2,6 +2,7 @@ import * as Schema from "effect/Schema"; import { GitPreparePullRequestThreadInput, GitRunStackedActionInput } from "./git.ts"; import { AuthAccessWriteScope, + AuthFilesystemWriteScope, AuthOrchestrationOperateScope, AuthOrchestrationReadScope, AuthPreviewOperateScope, @@ -36,6 +37,7 @@ export const CLIENT_GUARDED_RPC_SCOPES = { [WS_METHODS.pullRequestsSetLabels]: AuthSourceControlWriteScope, // Writing a guide runs a model on this environment's providers, which reading one does not. [WS_METHODS.pullRequestsGenerateReviewGuide]: AuthOrchestrationOperateScope, + [WS_METHODS.projectsWriteFile]: AuthFilesystemWriteScope, [WS_METHODS.projectsCreateFromThread]: AuthOrchestrationOperateScope, [WS_METHODS.sourceControlCloneRepository]: AuthSourceControlWriteScope, [WS_METHODS.sourceControlPublishRepository]: AuthSourceControlWriteScope,