Skip to content
Merged
41 changes: 41 additions & 0 deletions apps/server/src/auth/RpcAuthorization.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,3 +403,44 @@ it.effect("separates host file URLs from readable attachment URLs", () =>
expect(handled).toBe(1);
}).pipe(Effect.scoped),
);

it.effect("denies manual cleanup before the handler without settings permission", () =>
Effect.gen(function* () {
const group = WsRpcGroup.omit(
...[...WsRpcGroup.requests.keys()].filter(
(
tag,
): tag is Exclude<
keyof typeof RPC_REQUIRED_SCOPES,
typeof WS_METHODS.serverRunStorageCleanup
> => tag !== WS_METHODS.serverRunStorageCleanup,
),
);
let handled = false;
const client = yield* RpcTest.makeClient(group).pipe(
Effect.provide(
Layer.mergeAll(
group.toLayerHandler(WS_METHODS.serverRunStorageCleanup, () =>
Effect.sync(() => {
handled = true;
return {
trigger: "manual" as const,
startedAt: "2026-10-09T12:00:00.000Z",
finishedAt: "2026-10-09T12:00:00.000Z",
entries: [],
counts: { removed: 0, kept: 0, failed: 0 },
omittedCount: 0,
bytesFreed: 0,
};
}),
),
RpcAuthorization.layer([AuthOrchestrationReadScope]),
),
),
);
expect(yield* client[WS_METHODS.serverRunStorageCleanup]({}).pipe(Effect.flip)).toMatchObject({
requiredPermission: AuthSettingsWriteScope,
});
expect(handled).toBe(false);
}).pipe(Effect.scoped),
);
1 change: 1 addition & 0 deletions apps/server/src/auth/RpcAuthorization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export const RPC_REQUIRED_SCOPES = {
[WS_METHODS.serverCommitDesktopUpdate]: AuthEnvironmentMaintainScope,
[WS_METHODS.serverUpsertKeybinding]: AuthSettingsWriteScope,
[WS_METHODS.serverRemoveKeybinding]: AuthSettingsWriteScope,
[WS_METHODS.serverGetStorageCleanupReport]: AuthOrchestrationReadScope,
[WS_METHODS.serverGetSettings]: AuthOrchestrationReadScope,
[WS_METHODS.serverUpdateSettings]: AuthSettingsWriteScope,
[WS_METHODS.serverSearchAcpRegistry]: AuthOrchestrationReadScope,
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/environment/ServerEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ export const make = Effect.gen(function* () {
threadSettlement: true,
threadAutoSettlement: true,
storageCleanup: true,
storageCleanupRun: true,
projectWorktreeCleanup: true,
worktreesDirectory: true,
threadRestartContinuation: true,
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/observability/RpcInstrumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ const RPC_AGGREGATES = {
[WS_METHODS.serverCommitDesktopUpdate]: "server",
[WS_METHODS.serverUpsertKeybinding]: "server",
[WS_METHODS.serverRemoveKeybinding]: "server",
[WS_METHODS.serverRunStorageCleanup]: "server",
[WS_METHODS.serverGetStorageCleanupReport]: "server",
[WS_METHODS.serverGetSettings]: "server",
[WS_METHODS.serverUpdateSettings]: "server",
[WS_METHODS.serverSearchAcpRegistry]: "server",
Expand Down
4 changes: 1 addition & 3 deletions apps/server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -533,9 +533,7 @@ const layerRuntimeCoreDependenciesBase = Layer.mergeAll(
// Asks T3 Connect to deliver webhooks it held while this environment was offline.
HeldHooksWaker.layer,
layerThreadSettlementWorker,
Layer.effectDiscard(StorageCleanup.make.pipe(Effect.flatMap((service) => service.start()))).pipe(
Layer.provide(ProjectionStoreV2.layer),
),
StorageCleanup.layer.pipe(Layer.provide(ProjectionStoreV2.layer)),
layerThreadPullRequestWorker,
Layer.effectDiscard(
Effect.gen(function* () {
Expand Down
Loading
Loading