blob: 2142bbd8ce8a5e46dd9ebc4702f773769fa284e9 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { createReadOnlyPermissionProfile } from '@maka/core/permission-profile';
import {
createManagedExecutionBoundary,
type ExecutionBoundary,
} from '@maka/core/sandbox-boundary';
import type {
FilesystemWorkerClient,
FilesystemWorkerClientOperation,
} from '@maka/runtime/filesystem-worker';
export interface RuntimeHostWorkspaceExecutionProfile {
readonly kind: 'attached_checkout_v1';
readonly cwd: string;
}
export type RuntimeHostWorkspaceReadOnlyOperation = Extract<
FilesystemWorkerClientOperation,
{ kind: 'read' | 'glob' | 'grep' }
>;
export type RuntimeHostWorkspaceReadOnlyResult = Extract<
Awaited<ReturnType<FilesystemWorkerClient['execute']>>,
{ kind: 'read' | 'read_image' | 'glob' | 'grep' }
>;
export interface RuntimeHostWorkspaceFilesystemWorker {
execute(input: {
readonly operation: RuntimeHostWorkspaceReadOnlyOperation;
readonly cwd: string;
readonly executionBoundary: ExecutionBoundary;
readonly abortSignal?: AbortSignal;
}): Promise<RuntimeHostWorkspaceReadOnlyResult>;
}
export type RuntimeHostWorkspaceExecutionErrorCode =
| 'workspace_execution_draining'
| 'filesystem_worker_unavailable'
| 'workspace_operation_denied';
export class RuntimeHostWorkspaceExecutionError extends Error {
constructor(
readonly code: RuntimeHostWorkspaceExecutionErrorCode,
message: string,
) {
super(message);
this.name = 'RuntimeHostWorkspaceExecutionError';
}
}
export interface RuntimeHostWorkspaceExecutionComposition {
readonly state: 'ready' | 'draining' | 'closed';
executeReadOnly(
profile: RuntimeHostWorkspaceExecutionProfile,
operation: RuntimeHostWorkspaceReadOnlyOperation,
abortSignal?: AbortSignal,
): Promise<RuntimeHostWorkspaceReadOnlyResult>;
beginDrain(): void;
close(): Promise<void>;
}
export interface CreateRuntimeHostWorkspaceExecutionCompositionInput {
readonly filesystemWorker?: RuntimeHostWorkspaceFilesystemWorker;
}
export function createAttachedWorkspaceExecutionProfile(
cwd: string,
): RuntimeHostWorkspaceExecutionProfile {
if (typeof cwd !== 'string' || cwd.length === 0) {
throw new RuntimeHostWorkspaceExecutionError(
'workspace_operation_denied',
'Attached workspace execution requires a non-empty cwd',
);
}
return Object.freeze({ kind: 'attached_checkout_v1', cwd });
}
export function createRuntimeHostWorkspaceExecutionComposition(
input: CreateRuntimeHostWorkspaceExecutionCompositionInput,
): RuntimeHostWorkspaceExecutionComposition {
let state: RuntimeHostWorkspaceExecutionComposition['state'] = 'ready';
let activeOperations = 0;
const drainWaiters = new Set<() => void>();
let closeTask: Promise<void> | undefined;
const beginDrain = () => {
if (state === 'ready') state = 'draining';
};
const waitForDrain = () => {
if (activeOperations === 0) return Promise.resolve();
return new Promise<void>((resolve) => drainWaiters.add(resolve));
};
const finishOperation = () => {
activeOperations -= 1;
if (activeOperations !== 0) return;
for (const resolve of drainWaiters) resolve();
drainWaiters.clear();
};
return {
get state() {
return state;
},
async executeReadOnly(profile, operation, abortSignal) {
if (state !== 'ready') {
throw new RuntimeHostWorkspaceExecutionError(
'workspace_execution_draining',
'Runtime Host workspace execution is draining',
);
}
if (!isReadOnlyOperation(operation)) {
throw new RuntimeHostWorkspaceExecutionError(
'workspace_operation_denied',
'Runtime Host workspace execution permits only Read, Glob, and Grep',
);
}
if (!isWorkspaceExecutionProfile(profile)) {
throw new RuntimeHostWorkspaceExecutionError(
'workspace_operation_denied',
'Runtime Host workspace execution profile is invalid',
);
}
activeOperations += 1;
try {
if (!input.filesystemWorker) {
throw new RuntimeHostWorkspaceExecutionError(
'filesystem_worker_unavailable',
'Attached workspace filesystem worker is unavailable',
);
}
return await input.filesystemWorker.execute({
operation,
cwd: profile.cwd,
executionBoundary: createManagedExecutionBoundary(createReadOnlyPermissionProfile(), 0),
...(abortSignal ? { abortSignal } : {}),
});
} finally {
finishOperation();
}
},
beginDrain,
close() {
closeTask ??= (async () => {
beginDrain();
await waitForDrain();
state = 'closed';
})();
return closeTask;
},
};
}
function isReadOnlyOperation(input: unknown): input is RuntimeHostWorkspaceReadOnlyOperation {
if (!input || typeof input !== 'object') return false;
const kind = (input as { kind?: unknown }).kind;
return kind === 'read' || kind === 'glob' || kind === 'grep';
}
function isWorkspaceExecutionProfile(
input: unknown,
): input is RuntimeHostWorkspaceExecutionProfile {
if (!input || typeof input !== 'object') return false;
const candidate = input as {
kind?: unknown;
cwd?: unknown;
};
return (
candidate.kind === 'attached_checkout_v1' &&
typeof candidate.cwd === 'string' &&
candidate.cwd.length > 0
);
}