Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 20 additions & 10 deletions apps/desktop/src/main/__tests__/computer-use-host.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ describe('Computer Use host health', () => {
assert.equal(computerUseServiceHealth('none', undefined).state, 'not_available');
});

it('constructs a backend only when the local artifact matches the manifest hash', async () => {
it('constructs a backend only when the local artifact matches the manifest hash', async (t) => {
const directory = await mkdtemp(join(tmpdir(), 'maka-cu-host-'));
try {
const binaryPath = join(directory, 'maka-cu');
Expand Down Expand Up @@ -126,16 +126,26 @@ describe('Computer Use host health', () => {
});
assert.equal(invalid.selected.backendId, 'none');

const linkedBinaryPath = join(directory, 'linked-maka-cu');
await symlink(binaryPath, linkedBinaryPath);
const linked = createComputerUseHost({
isPackaged: false,
resourcesPath: directory,
manifestPath,
binaryPath: linkedBinaryPath,
physicalInputRecentlyActive: () => false,
await t.test('rejects a symlinked binary', async (context) => {
const linkedBinaryPath = join(directory, 'linked-maka-cu');
try {
await symlink(binaryPath, linkedBinaryPath, 'file');
} catch (error) {
if (process.platform === 'win32' && (error as NodeJS.ErrnoException).code === 'EPERM') {
context.skip('File symlinks require Developer Mode or symlink privileges on Windows');
return;
}
throw error;
}
const linked = createComputerUseHost({
isPackaged: false,
resourcesPath: directory,
manifestPath,
binaryPath: linkedBinaryPath,
physicalInputRecentlyActive: () => false,
});
assert.equal(linked.selected.backendId, 'none');
});
assert.equal(linked.selected.backendId, 'none');
} finally {
await rm(directory, { recursive: true, force: true });
}
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/__tests__/goals-boundary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ describe('Goals feature boundary', () => {
const source = readFileSync(path, 'utf8');
for (const match of source.matchAll(/from\s+['"]([^'"]+)['"]/g)) {
if (match[1]?.includes('controller/use-goal-controller')) {
importers.push(relative(desktopRoot, path));
importers.push(relative(desktopRoot, path).replace(/\\/g, '/'));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ description: Build decks.
const outside = join(root, 'outside-cache-target');
const cacheRoot = join(root, 'cache-link');
await mkdir(outside);
await symlink(outside, cacheRoot);
await symlink(outside, cacheRoot, process.platform === 'win32' ? 'junction' : 'dir');

assert.deepEqual(await importManagedSkillSource({ root: cacheRoot, sourceFile }), {
ok: false,
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src/main/__tests__/open-path-guard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe('open path guard', () => {
test('rejects symlink escapes from inside an allowed directory', async () => {
await withWorkspace(async (workspaceRoot, outsideRoot) => {
await mkdir(outsideRoot, { recursive: true });
await symlink(outsideRoot, join(workspaceRoot, 'skills'));
await symlink(outsideRoot, join(workspaceRoot, 'skills'), process.platform === 'win32' ? 'junction' : 'dir');

assert.deepEqual(await resolveOpenPath({ key: 'skills', workspaceRoot }), { ok: false, reason: 'not-allowed' });
});
Expand All @@ -89,7 +89,7 @@ describe('open path guard', () => {
const workspaceLink = join(linkRoot, 'workspace-link');
try {
await mkdir(join(realRoot, 'skills'), { recursive: true });
await symlink(realRoot, workspaceLink);
await symlink(realRoot, workspaceLink, process.platform === 'win32' ? 'junction' : 'dir');

const result = await resolveOpenPath({ key: 'skills', workspaceRoot: workspaceLink });

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ test('owns Project selection and reversible lifecycle actions in Desktop', async
assert.equal(relinked.ok, true);
assert.equal(selectedPaths.at(-1), await realpath(relocatedPath));
} finally {
catalog.close();
await rm(base, { recursive: true, force: true });
}
});
Expand Down
60 changes: 21 additions & 39 deletions apps/desktop/src/main/__tests__/rive-workflow-tool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/

import assert from 'node:assert/strict';
import { chmod, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { describe, it } from 'node:test';
Expand Down Expand Up @@ -141,7 +141,7 @@ describe('RiveWorkflow tool and CLI bridge', { concurrency: false }, () => {
});
});

it('kills and reaps a Rive child that ignores SIGTERM on abort', async () => {
it('kills and reaps an aborted Rive child, including ignored SIGTERM on POSIX', async () => {
await withFakeRive('ignore-term', async (riveBin, cwd) => {
const pidFile = join(cwd, 'pid');
const controller = new AbortController();
Expand Down Expand Up @@ -267,11 +267,13 @@ async function withFakeRive(
fn: (riveBin: string, cwd: string) => Promise<void>,
): Promise<void> {
const cwd = await mkdtemp(join(tmpdir(), 'maka-rive-tool-'));
const riveBin = join(cwd, 'rive');
await writeFile(riveBin, fakeRiveScript(mode), 'utf8');
await chmod(riveBin, 0o755);
try {
await fn(riveBin, cwd);
// Node treats the first Rive subcommand as a script path. This exercises a
// real shell-free child process on every platform without a shell shim.
for (const command of ['workflow', 'scheduler', 'work', 'branch']) {
await writeFile(join(cwd, command), fakeRiveScript(mode), 'utf8');
}
await fn(process.execPath, cwd);
} finally {
await rm(cwd, { recursive: true, force: true });
}
Expand All @@ -280,53 +282,33 @@ async function withFakeRive(
function fakeRiveScript(mode: string): string {
if (mode === 'ignore-term') {
return [
'#!/bin/sh',
'trap "" TERM',
'echo $$ > "$PID_FILE"',
'echo RIVE_CHILD_READY',
'sleep 20',
'echo \'{"protocol":{"state":"completed"},"display":{"summary":"late"}}\'',
'',
'process.on("SIGTERM", () => {});',
'require("node:fs").writeFileSync(process.env.PID_FILE, String(process.pid));',
'console.log("RIVE_CHILD_READY");',
'setTimeout(() => console.log(JSON.stringify({protocol: {state: "completed"}, display: {summary: "late"}})), 20_000);',
].join('\n');
}
if (mode === 'sleep') {
return [
'#!/bin/sh',
'sleep 5',
'echo \'{"protocol":{"state":"completed"},"display":{"summary":"late"}}\'',
'',
].join('\n');
return 'setTimeout(() => console.log(JSON.stringify({protocol: {state: "completed"}, display: {summary: "late"}})), 5_000);';
}
if (mode === 'bad-json') {
return ['#!/bin/sh', 'echo "not json"', ''].join('\n');
return 'console.log("not json");';
}
if (mode === 'failed-envelope') {
return [
'#!/bin/sh',
'cat <<\'JSON\'',
'{"error":{"code":"workflow_param_missing","message":"workflow missing param: slack_channel","action":"fix_arguments"}}',
'JSON',
'exit 1',
'',
'console.log(JSON.stringify({error: {code: "workflow_param_missing", message: "workflow missing param: slack_channel", action: "fix_arguments"}}));',
'process.exitCode = 1;',
].join('\n');
}
if (mode === 'failed-secret') {
return [
'#!/bin/sh',
'echo "api_key=abc123-super-secret" >&2',
'cat <<\'JSON\'',
'{"error":{"code":"auth","message":"token=abc123-super-secret"}}',
'JSON',
'exit 1',
'',
'console.error("api_key=abc123-super-secret");',
'console.log(JSON.stringify({error: {code: "auth", message: "token=abc123-super-secret"}}));',
'process.exitCode = 1;',
].join('\n');
}
return [
'#!/bin/sh',
'echo "token=abc123-super-secret" >&2',
'cat <<\'JSON\'',
'{"protocol":{"workflow_run_id":"wfrun_fake","scheduler_run_id":"sched_fake","root_work_node_id":"work_root_fake","state":"completed"},"display":{"summary":"Workflow run wfrun_fake root work_root_fake state completed"}}',
'JSON',
'',
'console.error("token=abc123-super-secret");',
'console.log(JSON.stringify({protocol: {workflow_run_id: "wfrun_fake", scheduler_run_id: "sched_fake", root_work_node_id: "work_root_fake", state: "completed"}, display: {summary: "Workflow run wfrun_fake root work_root_fake state completed"}}));',
].join('\n');
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ import type { createDesktopRuntimeHostLocalOperator } from '../runtime-host-loca

const testOperator = (modulePath: string) => ({
kind: 'node' as const,
platform: 'posix' as const,
nodePath: '/usr/bin/node',
platform: process.platform === 'win32' ? 'win32' as const : 'posix' as const,
nodePath: process.execPath,
modulePath,
});

Expand Down Expand Up @@ -518,7 +518,9 @@ test('does not persist recoverable setup authority before Desktop ownership comm
assert.equal(setupCalls, 0);
});

test('adopts a released handoff through its existing legacy operator', async (t) => {
test('adopts a released handoff through its existing legacy operator', {
skip: process.platform === 'win32' && 'Legacy POSIX handoff requires POSIX deployment paths',
}, async (t) => {
const base = await mkdtemp(join(tmpdir(), 'maka-local-remote-access-prestart-'));
t.after(() => rm(base, { recursive: true, force: true }));
const clientDataRoot = join(base, 'client');
Expand Down Expand Up @@ -593,7 +595,8 @@ test('migrates a released managed receipt before exposing it to lifecycle operat
const clientDataRoot = join(base, 'client');
const rootPath = join(clientDataRoot, 'workspaces', 'default');
const rootId = 'a'.repeat(64);
const operatorPath = join(base, 'installed', 'operator');
// The released schema describes a POSIX executable, regardless of the test host.
const operatorPath = '/opt/maka/installed/operator';
const lifecyclePath = join(clientDataRoot, 'runtime-host-local-service.json');
await mkdir(rootPath, { recursive: true });
await writeFile(
Expand Down
5 changes: 3 additions & 2 deletions docs/windows-test-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,17 @@ Locations intentionally omit line numbers so unrelated edits do not invalidate t
| Classification | Count |
|---|---:|
| windows-backend-gap | 27 |
| portable-candidate | 19 |
| portable-candidate | 20 |
| platform-contract | 31 |

Total Windows-excluded declarations: **77**
Total Windows-excluded declarations: **78**

## Inventory

| Classification | Test | Skip expression |
|---|---|---|
| platform-contract | `apps/desktop/src/main/__tests__/project-context-root.test.ts` rejects a session cwd without read and traversal access | `process.platform === 'win32' ? 'POSIX permissions are required to make the session cwd inaccessible' : process.getuid?.() === 0` |
| portable-candidate | `apps/desktop/src/main/__tests__/runtime-host-local-remote-access.test.ts` adopts a released handoff through its existing legacy operator | `process.platform === 'win32' && 'Legacy POSIX handoff requires POSIX deployment paths'` |
| platform-contract | `apps/desktop/src/main/__tests__/shell-env.test.ts` imports the login PATH without importing application control variables | `process.platform === 'win32'` |
| platform-contract | `apps/desktop/src/main/__tests__/shell-env.test.ts` keeps the inherited PATH and does not log shell stderr when capture fails | `process.platform === 'win32'` |
| platform-contract | `apps/desktop/src/main/__tests__/shell-env.test.ts` kills login-shell descendants when capture times out | `process.platform === 'win32'` |
Expand Down