From ec39986d66d2db7e1b3c36b436321559d5d9e852 Mon Sep 17 00:00:00 2001 From: M4n5ter Date: Fri, 4 Sep 2026 18:35:58 +0800 Subject: [PATCH 1/2] fix(desktop): stabilize Windows remote access setup Generated-by: OpenAI Codex --- .../runtime-host-local-operator.test.ts | 69 +++++++++++++++++++ .../src/main/runtime-host-local-operator.ts | 19 ++++- .../src/__tests__/runtime-host-setup.test.ts | 69 +++++++++++++++++++ .../src/runtime-host-managed-deployment.ts | 69 +++++++++++++++++-- .../src/runtime-host-package-deployment.ts | 1 + ...ime-host-windows-task-launcher-artifact.ts | 21 ++++++ 6 files changed, 243 insertions(+), 5 deletions(-) diff --git a/apps/desktop/src/main/__tests__/runtime-host-local-operator.test.ts b/apps/desktop/src/main/__tests__/runtime-host-local-operator.test.ts index 8b57c82549..5a08954585 100644 --- a/apps/desktop/src/main/__tests__/runtime-host-local-operator.test.ts +++ b/apps/desktop/src/main/__tests__/runtime-host-local-operator.test.ts @@ -133,6 +133,75 @@ test('local setup forwards the exact development archive evidence', async (t) => assert.equal(environment?.[RUNTIME_HOST_SETUP_SOURCE_PACKAGE_INTEGRITY_ENV], integrity); }); +test('local setup scratch cleanup cannot replace its framed outcome', async (t) => { + const frames = [ + { + schemaVersion: 1 as const, + sequence: 0, + kind: 'complete' as const, + version: '0.2.0', + serviceId: 'b'.repeat(64), + deploymentId: '00000000-0000-4000-8000-000000000001', + operator: OPERATOR, + rootPath: '/tmp/maka/root', + rootId: 'a'.repeat(64), + endpoint: 'ws://127.0.0.1:7443/runtime-host', + credentialId: 'credential-1', + credential: 'secret-access-token', + }, + { + schemaVersion: 1 as const, + sequence: 0, + kind: 'error' as const, + error: { code: 'setup_failed', message: 'primary setup failure' }, + }, + ]; + let invocation = 0; + let cleanupCount = 0; + const spawnProcess = (() => { + const child = new EventEmitter() as ReturnType; + const stdout = new PassThrough(); + const stderr = new PassThrough(); + const frame = frames[invocation++]; + Object.assign(child, { pid: 1234, stdout, stderr, kill: () => true }); + process.nextTick(() => { + stdout.end(encodeRuntimeHostSetupFrame(frame)); + stderr.end(); + child.emit('close', frame.kind === 'complete' ? 0 : 1, null); + }); + return child; + }) as typeof spawn; + const operator = createDesktopRuntimeHostLocalOperator({ + environment: { PATH: process.env.PATH }, + spawnProcess, + removeSetupWorkingDirectory: async (path) => { + cleanupCount += 1; + await rm(path, { recursive: true, force: true }); + throw new Error('scratch cleanup failed'); + }, + }); + t.after(() => operator.close()); + const setup = { + setupPackage: { kind: 'npm' as const, specifier: 'maka-agent@0.2.0' }, + clientDataRoot: '/tmp/maka/client', + rootPath: '/tmp/maka/root', + principalId: 'desktop-owner:pairing', + expectedTarget: { + serviceId: 'b'.repeat(64), + rootPath: '/tmp/maka/root', + rootId: 'a'.repeat(64), + }, + }; + + const complete = await operator.runSetup(setup, () => undefined); + assert.equal(complete.kind, 'complete'); + await assert.rejects( + operator.runSetup(setup, () => undefined), + /primary setup failure/u, + ); + assert.equal(cleanupCount, 2); +}); + test('Windows npm discovery cannot outlive setup cancellation', async (t) => { const originalPlatform = process.platform; const fixtureRoot = await mkdtemp(join(tmpdir(), 'maka-windows-npm-lookup-')); diff --git a/apps/desktop/src/main/runtime-host-local-operator.ts b/apps/desktop/src/main/runtime-host-local-operator.ts index 4b00e73885..1ade55cedc 100644 --- a/apps/desktop/src/main/runtime-host-local-operator.ts +++ b/apps/desktop/src/main/runtime-host-local-operator.ts @@ -62,6 +62,8 @@ import { const SETUP_TIMEOUT_MS = 10 * 60_000; const SETUP_FRAME_PENDING_MAX = 20 * 1024; const STDERR_MAX_BYTES = 64 * 1024; +const SETUP_CLEANUP_MAX_RETRIES = 10; +const SETUP_CLEANUP_RETRY_DELAY_MS = 100; const WINDOWS_NPM_RESOLUTION_SCRIPT = String.raw` const { statSync } = require('node:fs'); const path = require('node:path').win32; @@ -199,6 +201,7 @@ export function createDesktopRuntimeHostLocalOperator(input: { readonly spawnProcess?: typeof spawn; readonly setupTimeoutMs?: number; readonly terminateProcess?: typeof terminateChildProcessTree; + readonly removeSetupWorkingDirectory?: (path: string) => Promise; } = {}): { runSetup( setup: DesktopRuntimeHostLocalSetupInput, @@ -267,6 +270,15 @@ export function createDesktopRuntimeHostLocalOperator(input: { let closed = false; const closing = new AbortController(); const terminate = input.terminateProcess ?? terminateChildProcessTree; + const removeSetupWorkingDirectory = + input.removeSetupWorkingDirectory ?? + ((path: string) => + rm(path, { + recursive: true, + force: true, + maxRetries: SETUP_CLEANUP_MAX_RETRIES, + retryDelay: SETUP_CLEANUP_RETRY_DELAY_MS, + })); return { async runSetup(setup, onProgress) { @@ -302,7 +314,12 @@ export function createDesktopRuntimeHostLocalOperator(input: { active, }); } finally { - await rm(workingDirectory, { recursive: true, force: true }); + try { + await removeSetupWorkingDirectory(workingDirectory); + } catch { + // The private scratch directory is not part of the setup transaction. + // Its cleanup must not replace the operator's framed result or error. + } } }, runPeer(command) { diff --git a/packages/cli/src/__tests__/runtime-host-setup.test.ts b/packages/cli/src/__tests__/runtime-host-setup.test.ts index a8818d2783..041b4dd80c 100644 --- a/packages/cli/src/__tests__/runtime-host-setup.test.ts +++ b/packages/cli/src/__tests__/runtime-host-setup.test.ts @@ -54,12 +54,19 @@ import { acknowledgeRuntimeHostManagedDeploymentCleanup, assertRuntimeHostManagedOperatorDeployment, convergeRuntimeHostManagedOperator, + convergeRuntimeHostManagedWindowsTaskLauncher, prepareRuntimeHostManagedPackageDeployment, pruneRuntimeHostManagedPackages, readRuntimeHostManagedDeploymentCleanupReceipt, resolveRuntimeHostManagedControlRoot, resolveRuntimeHostManagedDeploymentRoot, + verifyRuntimeHostManagedWindowsTaskLauncher, } from '../runtime-host-managed-deployment.js'; +import { + resolvePackagedRuntimeHostWindowsTaskLauncherPath, + resolveRuntimeHostWindowsTaskLauncherPath, + runtimeHostManagedWindowsTaskLauncherPath, +} from '../runtime-host-windows-task-launcher-artifact.js'; import { runRuntimeHostSetupCli } from '../runtime-host-setup-command.js'; import { RuntimeHostAccessUnavailableError } from '../runtime-host-access-command.js'; import { replaceRuntimeHostLifecycle } from '../runtime-host-lifecycle-transaction.js'; @@ -977,6 +984,68 @@ test('managed operator binds its Client Data Root and routes deployment cleanup' assert.deepEqual(signalExit, { code: null, signal: 'SIGTERM' }); }); +test('managed Windows task launcher is projected to a stable deployment path', async (t) => { + const base = await mkdtemp(join(tmpdir(), 'maka-runtime-host-windows-launcher-')); + t.after(() => rm(base, { recursive: true, force: true })); + const sourcePackageRoot = await createReleasePackage(base, '0.2.0'); + const sourceLauncher = join( + sourcePackageRoot, + 'native', + 'runtime-host-windows-task-launcher', + 'prebuilds', + 'win32-x64', + 'maka-runtime-host-task-launcher.exe', + ); + await mkdir(dirname(sourceLauncher), { recursive: true }); + await writeFile(sourceLauncher, 'launcher-v1'); + const serviceId = 'a'.repeat(64); + const deployment = await prepareRuntimeHostManagedPackageDeployment( + { + serviceId, + clientDataRoot: join(base, 'client'), + sourcePackageRoot, + version: '0.2.0', + packageIntegrity: PACKAGE_INTEGRITY, + }, + { + env: { XDG_DATA_HOME: join(base, 'data') }, + homeDir: join(base, 'home'), + platform: 'linux', + }, + ); + const config: RuntimeHostManagedDeploymentConfig = { + schemaVersion: 1, + state: 'active', + deploymentId: '00000000-0000-4000-8000-000000000001', + configRevision: 1, + deploymentRoot: deployment.root, + root: { id: serviceId, path: join(base, 'state') }, + projectDirectoryRoots: [], + launch: { + kind: 'exact_package', + nodePath: process.execPath, + package: { kind: 'npm_registry', version: '0.2.0', integrity: PACKAGE_INTEGRITY }, + }, + listeners: { localIpc: true }, + lifecycle: { mode: 'supervised', provider: 'windows_task', availability: 'session' }, + reconciliation: { trigger: 'scheduled', provider: 'windows_task_timer' }, + }; + + await convergeRuntimeHostManagedWindowsTaskLauncher(config); + const projected = runtimeHostManagedWindowsTaskLauncherPath(deployment.root); + assert.equal(await readFile(projected, 'utf8'), 'launcher-v1'); + assert.equal(await resolveRuntimeHostWindowsTaskLauncherPath(deployment.cliPath), projected); + + const packaged = await resolvePackagedRuntimeHostWindowsTaskLauncherPath(deployment.cliPath); + await writeFile(packaged, 'launcher-v2'); + await assert.rejects( + verifyRuntimeHostManagedWindowsTaskLauncher(config), + /does not match its deployment/u, + ); + await convergeRuntimeHostManagedWindowsTaskLauncher(config); + await verifyRuntimeHostManagedWindowsTaskLauncher(config); +}); + async function createReleasePackage(base: string, version: string): Promise { const root = join(base, `source-package-${version}`); await mkdir(join(root, 'dist'), { recursive: true }); diff --git a/packages/cli/src/runtime-host-managed-deployment.ts b/packages/cli/src/runtime-host-managed-deployment.ts index 30e46b997a..95ce468e9d 100644 --- a/packages/cli/src/runtime-host-managed-deployment.ts +++ b/packages/cli/src/runtime-host-managed-deployment.ts @@ -41,6 +41,10 @@ import { type RuntimeHostManagedDeploymentAuthorityOptions, type RuntimeHostManagedDeploymentConfig, } from '@maka/runtime-host/operator'; +import { + resolvePackagedRuntimeHostWindowsTaskLauncherPath, + runtimeHostManagedWindowsTaskLauncherPath, +} from './runtime-host-windows-task-launcher-artifact.js'; export { RuntimeHostPackageDeploymentError as RuntimeHostManagedDeploymentError } from './runtime-host-package-deployment.js'; @@ -63,6 +67,7 @@ interface RuntimeHostManagedDeploymentCleanupReceipt { } const CLEANUP_RECEIPT_FILE = 'cleanup-approved.json'; +const WINDOWS_TASK_LAUNCHER_MAX_BYTES = 1024 * 1024; export function resolveRuntimeHostManagedPackageCliPath( deploymentRoot: string, @@ -641,15 +646,15 @@ async function writeOperatorLauncher( managedRootId, deploymentId, ); - await writeStableOperator(path, contents); + await writeStableArtifact(path, contents); } -async function writeStableOperator(path: string, contents: string): Promise { +async function writeStableArtifact(path: string, contents: string | Uint8Array): Promise { const temporaryPath = `${path}.${randomUUID()}.tmp`; try { const file = await open(temporaryPath, 'wx', 0o700); try { - await file.writeFile(contents, 'utf8'); + await file.writeFile(contents); await file.sync(); } finally { await file.close(); @@ -735,6 +740,9 @@ export async function convergeRuntimeHostManagedOperator( desired.root.id, desired.deploymentId, ); + if (process.platform === 'win32') { + await convergeRuntimeHostManagedWindowsTaskLauncher(desired); + } await forwardLegacyOperatorIfPresent( deployment.deploymentRoot, desired.launch.nodePath, @@ -761,7 +769,7 @@ async function forwardLegacyOperatorIfPresent( }, ); if (exists) { - await writeStableOperator(path, legacyOperatorLauncherContents(nodePath, modulePath)); + await writeStableArtifact(path, legacyOperatorLauncherContents(nodePath, modulePath)); } } @@ -811,6 +819,9 @@ export async function verifyRuntimeHostManagedOperator( cause: error, }); }); + if (process.platform === 'win32') { + await verifyRuntimeHostManagedWindowsTaskLauncher(config, { allowAbsent: true }); + } const legacyOperatorPath = join(config.deploymentRoot, 'operator'); const legacyExpected = legacyOperatorLauncherContents(config.launch.nodePath, operatorPath); const legacyExists = await access(legacyOperatorPath, constants.F_OK).then( @@ -839,6 +850,56 @@ export async function verifyRuntimeHostManagedOperator( } } +export async function convergeRuntimeHostManagedWindowsTaskLauncher( + config: RuntimeHostManagedDeploymentConfig, +): Promise { + const layout = resolveRuntimeHostNpmDeploymentLayout( + config.deploymentRoot, + config.launch.package.integrity, + ); + const sourcePath = await resolvePackagedRuntimeHostWindowsTaskLauncherPath(layout.cliPath); + await writeStableArtifact( + runtimeHostManagedWindowsTaskLauncherPath(config.deploymentRoot), + await readWindowsTaskLauncher(sourcePath), + ); +} + +export async function verifyRuntimeHostManagedWindowsTaskLauncher( + config: RuntimeHostManagedDeploymentConfig, + options: { readonly allowAbsent?: boolean } = {}, +): Promise { + const projectedPath = runtimeHostManagedWindowsTaskLauncherPath(config.deploymentRoot); + const projectedExists = await access(projectedPath, constants.F_OK).then( + () => true, + (error: unknown) => { + if (isNodeError(error, 'ENOENT')) return false; + throw error; + }, + ); + if (!projectedExists && options.allowAbsent) return; + + const layout = resolveRuntimeHostNpmDeploymentLayout( + config.deploymentRoot, + config.launch.package.integrity, + ); + const sourcePath = await resolvePackagedRuntimeHostWindowsTaskLauncherPath(layout.cliPath); + const [expected, observed] = await Promise.all([ + readWindowsTaskLauncher(sourcePath), + readWindowsTaskLauncher(projectedPath), + ]); + if (!observed.equals(expected)) { + throw new Error('The managed Runtime Host Windows task launcher does not match its deployment'); + } +} + +function readWindowsTaskLauncher(path: string): Promise { + return readStableBoundedFile({ + path, + maxBytes: WINDOWS_TASK_LAUNCHER_MAX_BYTES, + invalidFile: () => new Error('The managed Runtime Host Windows task launcher is invalid'), + }); +} + function isRecord(value: unknown): value is Record { return value !== null && typeof value === 'object' && !Array.isArray(value); } diff --git a/packages/cli/src/runtime-host-package-deployment.ts b/packages/cli/src/runtime-host-package-deployment.ts index 18d1968b41..ff2f7a7cf8 100644 --- a/packages/cli/src/runtime-host-package-deployment.ts +++ b/packages/cli/src/runtime-host-package-deployment.ts @@ -369,6 +369,7 @@ const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); await rm(path, { recursive: true, force: true, maxRetries: 100, retryDelay: 100 }); })().catch(() => { process.exitCode = 1; });`; const cleanup = spawn(process.execPath, ['-e', script, path, String(process.pid)], { + cwd: dirname(process.execPath), detached: true, stdio: 'ignore', windowsHide: true, diff --git a/packages/cli/src/runtime-host-windows-task-launcher-artifact.ts b/packages/cli/src/runtime-host-windows-task-launcher-artifact.ts index 5045017dd3..84ecb76ea3 100644 --- a/packages/cli/src/runtime-host-windows-task-launcher-artifact.ts +++ b/packages/cli/src/runtime-host-windows-task-launcher-artifact.ts @@ -23,6 +23,16 @@ import { basename, dirname, join } from 'node:path'; const LAUNCHER_FILE = 'maka-runtime-host-task-launcher.exe'; export async function resolveRuntimeHostWindowsTaskLauncherPath(cliPath: string): Promise { + const packageRoot = dirname(dirname(await realpath(cliPath))); + const projected = projectedLauncherPath(packageRoot); + if (projected && (await isReadable(projected))) return realpath(projected); + + return resolvePackagedRuntimeHostWindowsTaskLauncherPath(cliPath); +} + +export async function resolvePackagedRuntimeHostWindowsTaskLauncherPath( + cliPath: string, +): Promise { const packageRoot = dirname(dirname(await realpath(cliPath))); const packaged = join( packageRoot, @@ -51,6 +61,17 @@ export async function resolveRuntimeHostWindowsTaskLauncherPath(cliPath: string) throw new Error('Maka does not include the Windows Runtime Host task launcher'); } +export function runtimeHostManagedWindowsTaskLauncherPath(deploymentRoot: string): string { + return join(deploymentRoot, LAUNCHER_FILE); +} + +function projectedLauncherPath(packageRoot: string): string | undefined { + const versionsRoot = dirname(packageRoot); + return basename(versionsRoot) === 'versions' + ? runtimeHostManagedWindowsTaskLauncherPath(dirname(versionsRoot)) + : undefined; +} + async function isReadable(path: string): Promise { try { await access(path); From 461cc994af9497152d30c86f8dbfa914f1500149 Mon Sep 17 00:00:00 2001 From: M4n5ter Date: Fri, 4 Sep 2026 20:25:37 +0800 Subject: [PATCH 2/2] fix(runtime-host): make Windows task launchers immutable --- .../src/__tests__/runtime-host-setup.test.ts | 9 +++- .../src/runtime-host-managed-deployment.ts | 54 ++++++++++--------- ...ime-host-windows-task-launcher-artifact.ts | 35 +++++++++--- 3 files changed, 65 insertions(+), 33 deletions(-) diff --git a/packages/cli/src/__tests__/runtime-host-setup.test.ts b/packages/cli/src/__tests__/runtime-host-setup.test.ts index 041b4dd80c..bf8c8bc2c2 100644 --- a/packages/cli/src/__tests__/runtime-host-setup.test.ts +++ b/packages/cli/src/__tests__/runtime-host-setup.test.ts @@ -1032,7 +1032,10 @@ test('managed Windows task launcher is projected to a stable deployment path', a }; await convergeRuntimeHostManagedWindowsTaskLauncher(config); - const projected = runtimeHostManagedWindowsTaskLauncherPath(deployment.root); + const projected = runtimeHostManagedWindowsTaskLauncherPath( + deployment.root, + Buffer.from('launcher-v1'), + ); assert.equal(await readFile(projected, 'utf8'), 'launcher-v1'); assert.equal(await resolveRuntimeHostWindowsTaskLauncherPath(deployment.cliPath), projected); @@ -1044,6 +1047,10 @@ test('managed Windows task launcher is projected to a stable deployment path', a ); await convergeRuntimeHostManagedWindowsTaskLauncher(config); await verifyRuntimeHostManagedWindowsTaskLauncher(config); + assert.notEqual( + runtimeHostManagedWindowsTaskLauncherPath(deployment.root, Buffer.from('launcher-v2')), + projected, + ); }); async function createReleasePackage(base: string, version: string): Promise { diff --git a/packages/cli/src/runtime-host-managed-deployment.ts b/packages/cli/src/runtime-host-managed-deployment.ts index 95ce468e9d..4f383449a9 100644 --- a/packages/cli/src/runtime-host-managed-deployment.ts +++ b/packages/cli/src/runtime-host-managed-deployment.ts @@ -42,6 +42,7 @@ import { type RuntimeHostManagedDeploymentConfig, } from '@maka/runtime-host/operator'; import { + readRuntimeHostWindowsTaskLauncher, resolvePackagedRuntimeHostWindowsTaskLauncherPath, runtimeHostManagedWindowsTaskLauncherPath, } from './runtime-host-windows-task-launcher-artifact.js'; @@ -67,8 +68,6 @@ interface RuntimeHostManagedDeploymentCleanupReceipt { } const CLEANUP_RECEIPT_FILE = 'cleanup-approved.json'; -const WINDOWS_TASK_LAUNCHER_MAX_BYTES = 1024 * 1024; - export function resolveRuntimeHostManagedPackageCliPath( deploymentRoot: string, version: string, @@ -858,17 +857,36 @@ export async function convergeRuntimeHostManagedWindowsTaskLauncher( config.launch.package.integrity, ); const sourcePath = await resolvePackagedRuntimeHostWindowsTaskLauncherPath(layout.cliPath); - await writeStableArtifact( - runtimeHostManagedWindowsTaskLauncherPath(config.deploymentRoot), - await readWindowsTaskLauncher(sourcePath), + const expected = await readRuntimeHostWindowsTaskLauncher(sourcePath); + const projectedPath = runtimeHostManagedWindowsTaskLauncherPath(config.deploymentRoot, expected); + const projectedExists = await access(projectedPath, constants.F_OK).then( + () => true, + (error: unknown) => { + if (isNodeError(error, 'ENOENT')) return false; + throw error; + }, ); + if (projectedExists) { + const observed = await readRuntimeHostWindowsTaskLauncher(projectedPath); + if (!observed.equals(expected)) { + throw new Error('The managed Runtime Host Windows task launcher is invalid'); + } + return; + } + await writeStableArtifact(projectedPath, expected); } export async function verifyRuntimeHostManagedWindowsTaskLauncher( config: RuntimeHostManagedDeploymentConfig, options: { readonly allowAbsent?: boolean } = {}, ): Promise { - const projectedPath = runtimeHostManagedWindowsTaskLauncherPath(config.deploymentRoot); + const layout = resolveRuntimeHostNpmDeploymentLayout( + config.deploymentRoot, + config.launch.package.integrity, + ); + const sourcePath = await resolvePackagedRuntimeHostWindowsTaskLauncherPath(layout.cliPath); + const expected = await readRuntimeHostWindowsTaskLauncher(sourcePath); + const projectedPath = runtimeHostManagedWindowsTaskLauncherPath(config.deploymentRoot, expected); const projectedExists = await access(projectedPath, constants.F_OK).then( () => true, (error: unknown) => { @@ -876,30 +894,16 @@ export async function verifyRuntimeHostManagedWindowsTaskLauncher( throw error; }, ); - if (!projectedExists && options.allowAbsent) return; - - const layout = resolveRuntimeHostNpmDeploymentLayout( - config.deploymentRoot, - config.launch.package.integrity, - ); - const sourcePath = await resolvePackagedRuntimeHostWindowsTaskLauncherPath(layout.cliPath); - const [expected, observed] = await Promise.all([ - readWindowsTaskLauncher(sourcePath), - readWindowsTaskLauncher(projectedPath), - ]); + if (!projectedExists) { + if (options.allowAbsent) return; + throw new Error('The managed Runtime Host Windows task launcher does not match its deployment'); + } + const observed = await readRuntimeHostWindowsTaskLauncher(projectedPath); if (!observed.equals(expected)) { throw new Error('The managed Runtime Host Windows task launcher does not match its deployment'); } } -function readWindowsTaskLauncher(path: string): Promise { - return readStableBoundedFile({ - path, - maxBytes: WINDOWS_TASK_LAUNCHER_MAX_BYTES, - invalidFile: () => new Error('The managed Runtime Host Windows task launcher is invalid'), - }); -} - function isRecord(value: unknown): value is Record { return value !== null && typeof value === 'object' && !Array.isArray(value); } diff --git a/packages/cli/src/runtime-host-windows-task-launcher-artifact.ts b/packages/cli/src/runtime-host-windows-task-launcher-artifact.ts index 84ecb76ea3..d6adcb8ebf 100644 --- a/packages/cli/src/runtime-host-windows-task-launcher-artifact.ts +++ b/packages/cli/src/runtime-host-windows-task-launcher-artifact.ts @@ -17,17 +17,26 @@ * under the License. */ +import { createHash } from 'node:crypto'; import { access, realpath } from 'node:fs/promises'; import { basename, dirname, join } from 'node:path'; +import { readStableBoundedFile } from '@maka/storage/stable-storage'; const LAUNCHER_FILE = 'maka-runtime-host-task-launcher.exe'; +const MANAGED_LAUNCHER_PREFIX = 'maka-runtime-host-task-launcher-'; +const WINDOWS_TASK_LAUNCHER_MAX_BYTES = 1024 * 1024; export async function resolveRuntimeHostWindowsTaskLauncherPath(cliPath: string): Promise { const packageRoot = dirname(dirname(await realpath(cliPath))); - const projected = projectedLauncherPath(packageRoot); - if (projected && (await isReadable(projected))) return realpath(projected); + const packaged = await resolvePackagedRuntimeHostWindowsTaskLauncherPath(cliPath); + const contents = await readRuntimeHostWindowsTaskLauncher(packaged); + const projected = projectedLauncherPath(packageRoot, contents); + if (projected) { + const observed = await readRuntimeHostWindowsTaskLauncher(projected).catch(() => undefined); + if (observed?.equals(contents)) return realpath(projected); + } - return resolvePackagedRuntimeHostWindowsTaskLauncherPath(cliPath); + return packaged; } export async function resolvePackagedRuntimeHostWindowsTaskLauncherPath( @@ -61,14 +70,26 @@ export async function resolvePackagedRuntimeHostWindowsTaskLauncherPath( throw new Error('Maka does not include the Windows Runtime Host task launcher'); } -export function runtimeHostManagedWindowsTaskLauncherPath(deploymentRoot: string): string { - return join(deploymentRoot, LAUNCHER_FILE); +export function runtimeHostManagedWindowsTaskLauncherPath( + deploymentRoot: string, + contents: Uint8Array, +): string { + const digest = createHash('sha256').update(contents).digest('hex'); + return join(deploymentRoot, `${MANAGED_LAUNCHER_PREFIX}${digest}.exe`); +} + +export function readRuntimeHostWindowsTaskLauncher(path: string): Promise { + return readStableBoundedFile({ + path, + maxBytes: WINDOWS_TASK_LAUNCHER_MAX_BYTES, + invalidFile: () => new Error('The managed Runtime Host Windows task launcher is invalid'), + }); } -function projectedLauncherPath(packageRoot: string): string | undefined { +function projectedLauncherPath(packageRoot: string, contents: Uint8Array): string | undefined { const versionsRoot = dirname(packageRoot); return basename(versionsRoot) === 'versions' - ? runtimeHostManagedWindowsTaskLauncherPath(dirname(versionsRoot)) + ? runtimeHostManagedWindowsTaskLauncherPath(dirname(versionsRoot), contents) : undefined; }