From 2602632166c0b0b455580316869bb5bc79e6aa99 Mon Sep 17 00:00:00 2001 From: only21mil Date: Sat, 31 Oct 2026 19:31:24 +0000 Subject: [PATCH] fix(mobile): Save plan to workspace is off for connections without file write access projects.writeFile was missing from CLIENT_GUARDED_RPC_SCOPES, so its permissionAtom was always true and mobile's Save to workspace button stayed enabled for sessions without filesystem:write, failing only on the server. Move the entry into the client map that the server already spreads. Co-Authored-By: Claude Opus 5.5 --- apps/server/src/auth/RpcAuthorization.ts | 2 -- .../src/state/commandPermissions.test.ts | 22 +++++++++++++++++++ .../src/state/projectCommands.test.ts | 1 + .../contracts/src/clientRpcPermissions.ts | 2 ++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/apps/server/src/auth/RpcAuthorization.ts b/apps/server/src/auth/RpcAuthorization.ts index 6049121b41a5d..a7043010f9b61 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, @@ -148,7 +147,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 adfad46a41b89..7398f2ae063a9 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, @@ -96,6 +97,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 settings grant to run storage cleanup", () => 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 727dd87ff6864..4e6a44138aba5 100644 --- a/packages/client-runtime/src/state/projectCommands.test.ts +++ b/packages/client-runtime/src/state/projectCommands.test.ts @@ -124,6 +124,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 d3139f2eeff24..ff4d99d42ea45 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, @@ -33,6 +34,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,