Skip to content
Merged
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
135 changes: 134 additions & 1 deletion apps/desktop/src/main/__tests__/runtime-host-desktop-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type { BotIncomingMessage } from '@maka/runtime/bots';
import {
RuntimeHostOperationError,
RuntimeHostRequestInterruptedError,
type RuntimeHostSpawnedProcess,
} from '@maka/runtime-host/client';
import {
INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID,
Expand Down Expand Up @@ -270,6 +271,33 @@ test('does not retire the local Host twice when an update handoff triggers quit'
await owner.close();
});

test('does not block quit after a retired Local Host hands off to an unavailable supervisor', async () => {
const current = candidateHarness({ disconnectOnPrepare: true });
let starts = 0;
let reportFatal!: (error: Error) => void;
const fatalReported = new Promise<Error>((resolve) => {
reportFatal = resolve;
});
const owner = await startRuntimeHostDesktopManager({} as DesktopRuntimeHostCandidateStartInput, {
startCandidate: async () => {
starts += 1;
return starts === 1 ? ready(current.candidate) : incompatibleHost('wait_for_idle_exit');
},
waitForHostExit: async () => undefined,
onFatalError: reportFatal,
});

const handoff = await owner.retireOwnedLocalHost('interrupt_active_work');
assert.equal(handoff.kind, 'retired');
if (handoff.kind === 'retired') handoff.resume();
await fatalReported;

assert.deepEqual(await owner.retireOwnedLocalHost('interrupt_active_work'), {
kind: 'not_owned',
});
await owner.close();
});

test('coalesces concurrent retirement intents onto one exact Host request', async () => {
const current = candidateHarness({ disconnectOnPrepare: true });
let releaseExitWait!: () => void;
Expand Down Expand Up @@ -894,8 +922,55 @@ test('keeps reconnecting through transient startup failures until the Desktop ad
await owner.close();
});

test('reconciles interrupted managed setup after a Local discovery result', async () => {
const managed = candidateHarness({ ownership: 'supervised' });
const events: string[] = [];
let starts = 0;
const owner = await startRuntimeHostDesktopManager({} as DesktopRuntimeHostCandidateStartInput, {
startCandidate: async () => {
starts += 1;
events.push(`discover:${starts}`);
return starts === 1
? { kind: 'failed', reason: 'managed_root_requires_operator' }
: ready(managed.candidate);
},
recoverLocalHost: async () => {
events.push('reconcile');
return true;
},
});

assert.deepEqual(events, ['discover:1', 'reconcile', 'discover:2']);
assert.equal(owner.current('local')?.candidate?.hostOwnership, 'supervised');
await owner.close();
});

test('reconciles interrupted managed setup after Local discovery throws', async () => {
const managed = candidateHarness({ ownership: 'supervised' });
const events: string[] = [];
let starts = 0;
const owner = await startRuntimeHostDesktopManager({} as DesktopRuntimeHostCandidateStartInput, {
startCandidate: async () => {
starts += 1;
events.push(`discover:${starts}`);
if (starts === 1) throw new Error('managed deployment transition is in progress');
return ready(managed.candidate);
},
recoverLocalHost: async () => {
events.push('reconcile');
return true;
},
});

assert.deepEqual(events, ['discover:1', 'reconcile', 'discover:2']);
assert.equal(owner.current('local')?.candidate?.hostOwnership, 'supervised');
await owner.close();
});

test('stops reconnecting when the replacement Host is incompatible', async () => {
const first = candidateHarness();
const first = candidateHarness({
ownedProcess: { pid: 42, exited: new Promise(() => undefined) },
});
let reportFatal!: (error: Error) => void;
const fatalReported = new Promise<Error>((resolve) => {
reportFatal = resolve;
Expand All @@ -911,6 +986,62 @@ test('stops reconnecting when the replacement Host is incompatible', async () =>
await first.candidate.close();
const fatal = await fatalReported;
assert.match(fatal.message, /older Runtime Host/);
await assert.rejects(
owner.retireOwnedLocalHost('interrupt_active_work'),
(error: unknown) =>
error instanceof DesktopLocalHostRetirementError &&
error.facts.pid === 42 &&
error.cause === fatal,
);
await owner.close();
});

test('does not retain manual-stop authority after the owned Host process exits', async () => {
const first = candidateHarness({
ownedProcess: {
pid: 42,
exited: Promise.resolve({ code: 0, signal: null, stderr: '', stderrTruncated: false }),
},
});
let reportFatal!: (error: Error) => void;
const fatalReported = new Promise<Error>((resolve) => {
reportFatal = resolve;
});
const owner = await startRuntimeHostDesktopManager({} as DesktopRuntimeHostCandidateStartInput, {
startCandidate: async () =>
first.closeCalls === 0
? ready(first.candidate)
: incompatibleHost('wait_for_idle_exit'),
onFatalError: reportFatal,
});

await first.candidate.close();
await fatalReported;
assert.deepEqual(await owner.retireOwnedLocalHost('interrupt_active_work'), {
kind: 'not_owned',
});
await owner.close();
});

test('does not block quit after a supervised Local Host becomes permanently unavailable', async () => {
const first = candidateHarness({ ownership: 'supervised' });
let reportFatal!: (error: Error) => void;
const fatalReported = new Promise<Error>((resolve) => {
reportFatal = resolve;
});
const owner = await startRuntimeHostDesktopManager({} as DesktopRuntimeHostCandidateStartInput, {
startCandidate: async () =>
first.closeCalls === 0
? ready(first.candidate)
: incompatibleHost('wait_for_idle_exit'),
onFatalError: reportFatal,
});

await first.candidate.close();
await fatalReported;
assert.deepEqual(await owner.retireOwnedLocalHost('interrupt_active_work'), {
kind: 'not_owned',
});
await owner.close();
});

Expand Down Expand Up @@ -1160,6 +1291,7 @@ function candidateHarness(
disconnectOnPrepare?: boolean;
activeTasks?: boolean | 'always';
ownership?: 'owned_ephemeral' | 'supervised' | 'external';
ownedProcess?: RuntimeHostSpawnedProcess;
hostId?: string;
hostEpoch?: string;
finalizeFailures?: Error[];
Expand All @@ -1184,6 +1316,7 @@ function candidateHarness(
closed,
hostOwnership: options.ownership ?? 'owned_ephemeral',
hostPid: 42,
...(options.ownedProcess ? { ownedProcess: options.ownedProcess } : {}),
client: {
hostId: options.hostId ?? 'test-host',
hostEpoch: options.hostEpoch ?? 'test-host-epoch',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,175 @@ test('revokes the one Local sharing authority without changing peer connectivity
]);
});

test('an interrupted Local Host handoff converges to its exact managed service', async (t) => {
test('does not persist recoverable setup authority before Desktop ownership commits', async (t) => {
const base = await mkdtemp(join(tmpdir(), 'maka-local-remote-access-ownership-'));
t.after(() => rm(base, { recursive: true, force: true }));
const clientDataRoot = join(base, 'client');
const rootPath = join(clientDataRoot, 'workspaces', 'default');
const rootId = 'a'.repeat(64);
const lifecyclePath = join(clientDataRoot, 'runtime-host-local-service.json');
await mkdir(rootPath, { recursive: true });
const handlers = new Map<string, Parameters<Electron.IpcMain['handle']>[1]>();
let ownershipChecked = false;
let setupCalls = 0;
const service = createDesktopLocalRuntimeHostRemoteAccess({
ipcMain: {
handle: (channel, handler) => { handlers.set(channel, handler); },
removeHandler: (channel) => { handlers.delete(channel); },
},
clientDataRoot,
rootPath,
rootId,
directPeerAvailable: true,
manager: () =>
({
async retireOwnedLocalHost() {
ownershipChecked = true;
await assert.rejects(readFile(lifecyclePath, 'utf8'), { code: 'ENOENT' });
return { kind: 'not_owned' as const };
},
}) as unknown as RuntimeHostDesktopManager,
resolveSetupPackage: async () => ({ kind: 'npm', specifier: 'maka-agent@0.2.0' }),
operator: {
async runSetup() {
setupCalls += 1;
throw new Error('setup must not run for an externally managed Host');
},
async close() {},
} as unknown as ReturnType<typeof createDesktopRuntimeHostLocalOperator>,
});
t.after(() => service.close());

const enable = handlers.get('local-runtime-host-remote-access:enable');
assert.ok(enable);
const enabling = enable({} as Electron.IpcMainInvokeEvent, {
allowInterruptActiveTasks: false,
coordinationRelays: [],
});
await assert.rejects(enabling, /already managed outside this Desktop/u);
assert.equal(ownershipChecked, true);
await assert.rejects(readFile(lifecyclePath, 'utf8'), { code: 'ENOENT' });
assert.equal(setupCalls, 0);
});

test('adopts committed managed authority for every pending receipt without replaying setup', async (t) => {
const base = await mkdtemp(join(tmpdir(), 'maka-local-remote-access-prestart-'));
t.after(() => rm(base, { recursive: true, force: true }));
for (const state of ['handoff', 'setupPending'] as const) {
const clientDataRoot = join(base, state);
const rootPath = join(clientDataRoot, 'workspaces', 'default');
const rootId = 'a'.repeat(64);
const deploymentId = '22222222-2222-4222-8222-222222222222';
const operatorPath = join(base, 'installed', 'operator');
await mkdir(rootPath, { recursive: true });
await writeFile(
join(clientDataRoot, 'runtime-host-local-service.json'),
`${JSON.stringify({
schemaVersion: 1,
state,
rootPath,
rootId,
coordinationRelays: [],
allowInterruptActiveTasks: true,
})}\n`,
);
const service = createDesktopLocalRuntimeHostRemoteAccess({
ipcMain: { handle() {}, removeHandler() {} },
clientDataRoot,
rootPath,
rootId,
directPeerAvailable: true,
manager: () => assert.fail('pre-start reconciliation must not require the Local manager'),
resolveManagedDeploymentAuthority: async () => ({
kind: 'active',
target: {
schemaVersion: 1,
serviceId: rootId,
operatorPath,
rootPath,
rootId,
deploymentId,
},
}),
resolveSetupPackage: async () =>
assert.fail('committed authority must not resolve a package'),
operator: {
async runSetup() {
assert.fail('committed authority must not replay setup');
},
async close() {},
} as unknown as ReturnType<typeof createDesktopRuntimeHostLocalOperator>,
});
t.after(() => service.close());

assert.equal(await service.recoverManagedSetup(), true);
assert.deepEqual(
JSON.parse(await readFile(join(clientDataRoot, 'runtime-host-local-service.json'), 'utf8')),
{
schemaVersion: 1,
state: 'managed',
serviceId: rootId,
operatorPath,
rootPath,
rootId,
deploymentId,
},
);
}
});

test('discards a legacy handoff that belongs to an externally managed Host', async (t) => {
const base = await mkdtemp(join(tmpdir(), 'maka-local-remote-access-legacy-external-'));
t.after(() => rm(base, { recursive: true, force: true }));
const clientDataRoot = join(base, 'client');
const rootPath = join(clientDataRoot, 'workspaces', 'default');
const rootId = 'a'.repeat(64);
const lifecyclePath = join(clientDataRoot, 'runtime-host-local-service.json');
await mkdir(rootPath, { recursive: true });
await writeFile(
lifecyclePath,
`${JSON.stringify({
schemaVersion: 1,
state: 'handoff',
rootPath,
rootId,
coordinationRelays: [],
allowInterruptActiveTasks: false,
})}\n`,
);
let setupCalls = 0;
const service = createDesktopLocalRuntimeHostRemoteAccess({
ipcMain: { handle() {}, removeHandler() {} },
clientDataRoot,
rootPath,
rootId,
directPeerAvailable: true,
manager: () =>
({
async retireOwnedLocalHost() {
return { kind: 'not_owned' as const };
},
}) as unknown as RuntimeHostDesktopManager,
resolveManagedDeploymentAuthority: async () => undefined,
resolveSetupPackage: async () => ({ kind: 'npm', specifier: 'maka-agent@0.2.0' }),
operator: {
async runSetup() {
setupCalls += 1;
throw new Error('setup must not replace an externally managed Host');
},
async close() {},
} as unknown as ReturnType<typeof createDesktopRuntimeHostLocalOperator>,
});
t.after(() => service.close());

assert.equal(await service.recoverManagedSetup(), false);
await service.recover();

assert.equal(setupCalls, 0);
await assert.rejects(readFile(lifecyclePath, 'utf8'), { code: 'ENOENT' });
});

test('interrupted Local Host setup converges to its exact managed service', async (t) => {
const base = await mkdtemp(join(tmpdir(), 'maka-local-remote-access-recovery-'));
t.after(() => rm(base, { recursive: true, force: true }));
const clientDataRoot = join(base, 'client');
Expand All @@ -209,14 +377,15 @@ test('an interrupted Local Host handoff converges to its exact managed service',
join(clientDataRoot, 'runtime-host-local-service.json'),
`${JSON.stringify({
schemaVersion: 1,
state: 'handoff',
state: 'setupPending',
rootPath,
rootId,
coordinationRelays: [],
allowInterruptActiveTasks: true,
})}\n`,
);
let setupCalls = 0;
let setupQuiesced = false;
const service = createDesktopLocalRuntimeHostRemoteAccess({
ipcMain: { handle() {}, removeHandler() {} },
clientDataRoot,
Expand All @@ -229,7 +398,12 @@ test('an interrupted Local Host handoff converges to its exact managed service',
assert.equal(mode, 'interrupt_active_work');
return { kind: 'not_owned' as const };
},
async runManagedLocalHostChange(change: () => Promise<unknown>) {
setupQuiesced = true;
return change();
},
}) as unknown as RuntimeHostDesktopManager,
resolveManagedDeploymentAuthority: async () => undefined,
resolveSetupPackage: async () => ({ kind: 'npm', specifier: 'maka-agent@0.2.0' }),
operator: {
async runSetup() {
Expand All @@ -253,9 +427,12 @@ test('an interrupted Local Host handoff converges to its exact managed service',
});
t.after(() => service.close());

assert.equal(await service.recoverManagedSetup(), false);
assert.equal(setupCalls, 0);
await service.recover();

assert.equal(setupCalls, 1);
assert.equal(setupQuiesced, true);
assert.equal(
JSON.parse(await readFile(join(clientDataRoot, 'runtime-host-local-service.json'), 'utf8'))
.state,
Expand Down
Loading
Loading