blob: 44cda8ac9524e9fd056babd414923e73d9170850 [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 { deferred, withTimeout } from '@maka/core/test-only/async-primitives';
import { RuntimeHostProtocolError } from '../protocol/errors.js';
import { defineInteractiveRuntimeHostComposition } from '../server/host-composition.js';
import assert from 'node:assert/strict';
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import { execFile, fork, type ChildProcess } from 'node:child_process';
import {
chmod,
lstat,
mkdir,
mkdtemp,
readdir,
readFile,
rename,
stat,
writeFile,
rm,
unlink,
} from 'node:fs/promises';
import { createRequire } from 'node:module';
import { connect, Socket } from 'node:net';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { describe, test } from 'node:test';
import { promisify } from 'node:util';
import {
connectOrSpawnRuntimeHost,
connectRuntimeHost,
RuntimeHostOperationError,
RuntimeHostRequestInterruptedError,
type RuntimeHostConnection,
} from '../client/index.js';
import { connectOrSpawnRuntimeHostWithDependencies } from '../client/connect-or-spawn.js';
import {
launchDetachedRuntimeHostCandidate,
type DetachedCandidateAttempt,
type DetachedCandidateLaunch,
type DetachedCandidateInput,
} from '../client/launcher.js';
import { readHostRegistration, RUNTIME_HOST_REGISTRATION_FILE } from '../control/registration.js';
import {
readCandidateStartupDiagnostic,
writeCandidateStartupDiagnostic,
} from '../control/startup-diagnostic.js';
import { removePosixEndpointDirectories } from './fixtures/endpoint-hygiene.js';
import {
decodeHostFrame,
encodeProtocolMessage,
RUNTIME_HOST_COMPATIBILITY_EPOCH,
RUNTIME_HOST_MAX_MESSAGE_BYTES,
RUNTIME_HOST_PROTOCOL_VERSION,
RUNTIME_HOST_REGISTRATION_SCHEMA_VERSION,
type ClientFrame,
} from '../protocol/index.js';
import {
RuntimeHostKernel,
RuntimeHostProcessTerminationRequiredError,
type RuntimeHostComposition,
type RuntimeHostCompositionContext,
type RuntimeHostCompositionFactory,
type RuntimeHostKernelOptions,
} from '../server/host-kernel.js';
import {
startInteractiveRuntimeHostCandidate,
type InteractiveRuntimeHostCandidateOptions,
type InteractiveRuntimeHostCandidateResult,
} from '../server/candidate.js';
import type { RuntimeHostCompositionSource } from '../server/host-composition.js';
import { createUnavailableDomainOperationHandlers } from '../server/operation-dispatcher.js';
import { HostChangeFeed } from '../server/host-change-feed.js';
import { FramedTransport, RuntimeHostTransportError } from '../transport/framed-transport.js';
import {
prepareStorageRootControlDirectory,
resolveRootControlNamespace,
resolveStorageRoot,
STORAGE_ROOT_MARKER_FILE,
StorageRootAuthorityError,
tryAcquireInteractiveRootOwner,
type InteractiveRootOwner,
type StorageRootCapability,
} from '@maka/storage/root-authority';
import { bindStateRootComposition } from '@maka/storage/state-root-composition';
const CURRENT_PROTOCOL = {
min: RUNTIME_HOST_PROTOCOL_VERSION,
max: RUNTIME_HOST_PROTOCOL_VERSION,
} as const;
const LEGACY_PROTOCOL = { min: 1, max: 1 } as const;
const STARTUP_ATTEMPT_A = '00000000-0000-4000-8000-000000000001';
const STARTUP_ATTEMPT_B = '00000000-0000-4000-8000-000000000002';
const KERNEL_CANDIDATE_ENTRYPOINT = new URL('./fixtures/kernel-candidate.js', import.meta.url);
const KERNEL_COMPOSITION = defineInteractiveRuntimeHostComposition(async () => ({
handlers: createUnavailableDomainOperationHandlers(),
beginDrain() {},
async recover() {},
async close() {},
}));
const require = createRequire(import.meta.url);
const execFileAsync = promisify(execFile);
type IsExact<Left, Right> =
(<Value>() => Value extends Left ? 1 : 2) extends <Value>() => Value extends Right ? 1 : 2
? (<Value>() => Value extends Right ? 1 : 2) extends <Value>() => Value extends Left ? 1 : 2
? true
: false
: false;
type AssertTrue<Value extends true> = Value;
export type RuntimeHostInteractiveRootTypeContract = [
AssertTrue<IsExact<RuntimeHostCompositionContext['owner'], InteractiveRootOwner>>,
AssertTrue<IsExact<RuntimeHostKernelOptions['owner'], InteractiveRootOwner>>,
];
// @ts-expect-error Runtime Host composition contexts are concretely interactive.
export type GenericRuntimeHostCompositionContext = RuntimeHostCompositionContext<'interactive'>;
// @ts-expect-error Runtime Host composition factories are concretely interactive.
export type GenericRuntimeHostCompositionFactory = RuntimeHostCompositionFactory<'interactive'>;
// @ts-expect-error Runtime Host composition sources are concretely interactive.
export type GenericRuntimeHostCompositionSource = RuntimeHostCompositionSource<'interactive'>;
// @ts-expect-error Runtime Host kernel options are concretely interactive.
export type GenericRuntimeHostKernelOptions = RuntimeHostKernelOptions<'interactive'>;
function diagnosticRegistration(state: 'ready' | 'draining') {
return {
kind: 'maka-runtime-host',
schemaVersion: RUNTIME_HOST_REGISTRATION_SCHEMA_VERSION,
rootId: '00000000-0000-4000-8000-000000000003',
hostEpoch: '00000000-0000-4000-8000-000000000004',
endpoint: '\\\\.\\pipe\\maka-runtime-host-diagnostic',
protocolMin: CURRENT_PROTOCOL.min,
protocolMax: CURRENT_PROTOCOL.max,
compatibilityEpoch: RUNTIME_HOST_COMPATIBILITY_EPOCH,
compositionId: KERNEL_COMPOSITION.descriptor.id,
compositionRevision: KERNEL_COMPOSITION.descriptor.revision,
lifecycleMode: 'ephemeral',
state,
pid: 4242,
createdAt: '2026-08-22T00:00:00.000Z',
} as const;
}
describe('non-serving Runtime Host kernel', () => {
test('reports a recovery failure when the election produces no ready Host', async () => {
await withHostPaths(async (paths) => {
const result = await connectOrSpawnRuntimeHostWithDependencies(
{
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
compositionId: KERNEL_COMPOSITION.descriptor.id,
candidateEntrypoint: KERNEL_CANDIDATE_ENTRYPOINT,
electionDeadlineMs: 5_000,
},
{
random: () => 0.5,
launchCandidate: (input) =>
launchTestRuntimeHostCandidate(paths, {
...input,
env: {
MAKA_TEST_STARTUP_ERROR_CODE: 'stored_session_message_incompatible',
},
}),
},
);
assert.deepEqual(result, { kind: 'failed', reason: 'stored_data_incompatible' });
});
});
test('reports an operational migration blocker as a permanent election failure', async () => {
await withHostPaths(async (paths) => {
let launches = 0;
const result = await connectOrSpawnRuntimeHostWithDependencies(
{
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
compositionId: KERNEL_COMPOSITION.descriptor.id,
candidateEntrypoint: KERNEL_CANDIDATE_ENTRYPOINT,
electionDeadlineMs: 1_000,
},
{
random: () => 0.5,
launchCandidate: () => {
launches += 1;
return {
spawned: Promise.resolve({
pid: process.pid,
startupFailure: Promise.resolve({
reason: 'operational_state_migration_blocked' as const,
startupAttemptId: STARTUP_ATTEMPT_A,
}),
}),
};
},
},
);
assert.deepEqual(result, { kind: 'failed', reason: 'operational_state_migration_blocked' });
assert.equal(launches, 1);
});
});
test('treats a rejected Candidate report as unavailable election evidence', async () => {
await withHostPaths(async (paths) => {
const result = await connectOrSpawnRuntimeHostWithDependencies(
{
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
compositionId: KERNEL_COMPOSITION.descriptor.id,
candidateEntrypoint: KERNEL_CANDIDATE_ENTRYPOINT,
electionDeadlineMs: 100,
},
{
random: () => 0.5,
launchCandidate: () => ({
spawned: Promise.resolve({
pid: process.pid,
startupFailure: Promise.reject(new Error('report failed')),
}),
}),
},
);
assert.equal(result.kind, 'failed');
if (result.kind !== 'failed') return;
assert.equal(result.reason, 'startup_timeout');
assert.ok(result.diagnostic);
assert.equal(result.diagnostic.candidateLaunches, 1);
});
});
test('attributes an unresponsive endpoint to the exact running Candidate attempt', async () => {
await withHostPaths(async (paths) => {
let connectCalls = 0;
const result = await connectOrSpawnRuntimeHostWithDependencies(
{
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
compositionId: KERNEL_COMPOSITION.descriptor.id,
candidateEntrypoint: KERNEL_CANDIDATE_ENTRYPOINT,
electionDeadlineMs: 500,
},
{
random: () => 0.5,
connectHost: async () => {
connectCalls += 1;
return connectCalls === 1
? {
kind: 'unavailable' as const,
reason: 'not_registered' as const,
endpointConnected: false,
}
: { kind: 'election_deadline_elapsed' as const, endpointConnected: true };
},
launchCandidate: () => ({
spawned: Promise.resolve({
pid: 4242,
startupAttemptId: STARTUP_ATTEMPT_A,
exited: new Promise<never>(() => undefined),
startupFailure: new Promise<never>(() => undefined),
}),
}),
},
);
assert.equal(result.kind, 'failed');
if (result.kind !== 'failed') return;
assert.equal(result.reason, 'host_unresponsive');
assert.ok(result.diagnostic);
assert.equal(result.diagnostic.candidateLaunches, 1);
assert.equal(result.diagnostic.sawEndpointConnected, true);
assert.deepEqual(result.diagnostic.observations, {
totalResults: 2,
notRegistered: 1,
connectFailed: 0,
handshakeFailed: 0,
connected: 0,
readyWaitFailed: 0,
deadlineElapsed: 1,
otherResults: 0,
});
assert.deepEqual(result.diagnostic.latestCandidate, {
pid: 4242,
startupAttemptId: STARTUP_ATTEMPT_A,
state: 'running',
});
});
});
test('keeps diagnostic observation buckets reconcilable for unclassified results', async () => {
await withHostPaths(async (paths) => {
let connectCalls = 0;
const result = await connectOrSpawnRuntimeHostWithDependencies(
{
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
compositionId: KERNEL_COMPOSITION.descriptor.id,
candidateEntrypoint: KERNEL_CANDIDATE_ENTRYPOINT,
electionDeadlineMs: 500,
},
{
random: () => 0,
connectHost: async () => {
connectCalls += 1;
if (connectCalls === 1) {
return {
kind: 'draining' as const,
registration: diagnosticRegistration('draining'),
};
}
if (connectCalls === 2) {
return {
kind: 'unavailable' as const,
reason: 'invalid_registration' as const,
endpointConnected: false,
};
}
return { kind: 'election_deadline_elapsed' as const, endpointConnected: true };
},
launchCandidate: () => ({ spawned: Promise.resolve({ pid: process.pid }) }),
},
);
assert.equal(result.kind, 'failed');
if (result.kind !== 'failed') return;
assert.equal(result.reason, 'host_unresponsive');
assert.deepEqual(result.diagnostic?.observations, {
totalResults: 3,
notRegistered: 0,
connectFailed: 0,
handshakeFailed: 0,
connected: 0,
readyWaitFailed: 0,
deadlineElapsed: 1,
otherResults: 2,
});
const observations = result.diagnostic?.observations;
assert.equal(
observations?.totalResults,
(observations?.notRegistered ?? 0) +
(observations?.connectFailed ?? 0) +
(observations?.handshakeFailed ?? 0) +
(observations?.connected ?? 0) +
(observations?.deadlineElapsed ?? 0) +
(observations?.otherResults ?? 0),
);
});
});
for (const handshakeResult of [
{
name: 'draining',
result: {
kind: 'draining' as const,
registration: diagnosticRegistration('draining'),
},
},
{
name: 'non-blocking incompatible',
result: {
kind: 'incompatible' as const,
registration: diagnosticRegistration('ready'),
handshake: {
kind: 'incompatible' as const,
hostEpoch: '00000000-0000-4000-8000-000000000004',
protocolMin: CURRENT_PROTOCOL.min,
protocolMax: CURRENT_PROTOCOL.max,
compatibilityEpoch: RUNTIME_HOST_COMPATIBILITY_EPOCH,
compositionId: KERNEL_COMPOSITION.descriptor.id,
compositionRevision: KERNEL_COMPOSITION.descriptor.revision,
state: 'ready' as const,
replacement: 'wait_for_idle_exit' as const,
},
},
},
{
name: 'registration root mismatch',
result: {
kind: 'unavailable' as const,
reason: 'root_mismatch' as const,
endpointConnected: false,
registration: diagnosticRegistration('ready'),
},
expectedEndpointConnected: false,
},
{
name: 'handshake root mismatch',
result: {
kind: 'unavailable' as const,
reason: 'root_mismatch' as const,
endpointConnected: true,
registration: diagnosticRegistration('ready'),
},
expectedEndpointConnected: true,
},
{
name: 'epoch mismatch',
result: {
kind: 'unavailable' as const,
reason: 'epoch_mismatch' as const,
endpointConnected: true,
registration: diagnosticRegistration('ready'),
},
expectedEndpointConnected: true,
},
{
name: 'composition mismatch',
result: {
kind: 'unavailable' as const,
reason: 'composition_mismatch' as const,
endpointConnected: true,
registration: diagnosticRegistration('ready'),
},
expectedEndpointConnected: true,
},
]) {
test(`records ${handshakeResult.name} endpoint evidence exactly`, async () => {
await withHostPaths(async (paths) => {
const launch = {
spawned: Promise.resolve({
pid: 4242,
startupAttemptId: STARTUP_ATTEMPT_A,
exited: new Promise<never>(() => undefined),
startupFailure: new Promise<never>(() => undefined),
}),
};
const result = await connectOrSpawnRuntimeHostWithDependencies(
{
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
compositionId: KERNEL_COMPOSITION.descriptor.id,
candidateEntrypoint: KERNEL_CANDIDATE_ENTRYPOINT,
electionDeadlineMs: 300,
},
{
random: () => 0,
connectHost: async () => handshakeResult.result,
launchCandidate: () => launch,
},
);
assert.equal(result.kind, 'failed');
if (result.kind !== 'failed') return;
assert.equal(result.reason, 'startup_timeout');
assert.equal(
result.diagnostic?.sawEndpointConnected,
'expectedEndpointConnected' in handshakeResult
? handshakeResult.expectedEndpointConnected
: true,
);
});
});
}
test('keeps one live Candidate in flight for the whole election', async () => {
await withHostPaths(async (paths) => {
let launches = 0;
const result = await connectOrSpawnRuntimeHostWithDependencies(
{
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
compositionId: KERNEL_COMPOSITION.descriptor.id,
candidateEntrypoint: KERNEL_CANDIDATE_ENTRYPOINT,
electionDeadlineMs: 500,
},
{
random: () => 0,
launchCandidate: () => {
launches += 1;
return {
spawned: Promise.resolve({
pid: 4242,
exited: new Promise<never>(() => undefined),
}),
};
},
},
);
assert.equal(result.kind, 'failed');
if (result.kind !== 'failed') return;
assert.equal(result.reason, 'startup_timeout');
assert.equal(result.diagnostic?.candidateLaunches, 1);
assert.equal(launches, 1);
});
});
test('launches one successor after the exact in-flight Candidate exits', async () => {
await withHostPaths(async (paths) => {
let launches = 0;
const result = await connectOrSpawnRuntimeHostWithDependencies(
{
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
compositionId: KERNEL_COMPOSITION.descriptor.id,
candidateEntrypoint: KERNEL_CANDIDATE_ENTRYPOINT,
electionDeadlineMs: 700,
},
{
random: () => 0,
launchCandidate: () => {
launches += 1;
return {
spawned: Promise.resolve({
pid: 4241 + launches,
exited:
launches === 1
? new Promise((resolve) => {
setTimeout(
() =>
resolve({
code: 2,
signal: null,
stderr: '',
stderrTruncated: false,
}),
25,
);
})
: new Promise<never>(() => undefined),
}),
};
},
},
);
assert.equal(result.kind, 'failed');
if (result.kind !== 'failed') return;
assert.equal(result.reason, 'startup_timeout');
assert.equal(result.diagnostic?.candidateLaunches, 2);
assert.equal(launches, 2);
});
});
test('retries after a Candidate spawn is rejected before an attempt exists', async () => {
await withHostPaths(async (paths) => {
let launches = 0;
const result = await connectOrSpawnRuntimeHostWithDependencies(
{
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
compositionId: KERNEL_COMPOSITION.descriptor.id,
candidateEntrypoint: KERNEL_CANDIDATE_ENTRYPOINT,
electionDeadlineMs: 700,
},
{
random: () => 0,
launchCandidate: () => {
launches += 1;
return launches === 1
? { spawned: Promise.reject(new Error('spawn refused')) }
: {
spawned: Promise.resolve({
pid: 4242,
exited: new Promise<never>(() => undefined),
}),
};
},
},
);
assert.equal(result.kind, 'failed');
if (result.kind !== 'failed') return;
assert.equal(result.reason, 'startup_timeout');
assert.equal(result.diagnostic?.candidateLaunches, 2);
assert.equal(launches, 2);
});
});
test('publishes the diagnostic from the Candidate failure selected by the election', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
let launches = 0;
const result = await connectOrSpawnRuntimeHostWithDependencies(
{
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
compositionId: KERNEL_COMPOSITION.descriptor.id,
candidateEntrypoint: KERNEL_CANDIDATE_ENTRYPOINT,
electionDeadlineMs: 800,
},
{
random: () => 0.5,
launchCandidate: () => {
launches += 1;
if (launches > 2) return { spawned: new Promise(() => undefined) };
const startupAttemptId = launches === 1 ? STARTUP_ATTEMPT_A : STARTUP_ATTEMPT_B;
const reason =
launches === 1
? ('local_ipc_security_failed' as const)
: ('internal_startup_failure' as const);
return {
spawned: writeCandidateStartupDiagnostic({
rootId: capability.rootId,
startupAttemptId,
failure: { reason },
error: new Error(`Candidate ${launches} failed`),
}).then(() => ({
pid: process.pid,
startupFailure: Promise.resolve({ reason, startupAttemptId }),
})),
};
},
},
);
assert.deepEqual(result, { kind: 'failed', reason: 'local_ipc_security_failed' });
assert.equal(
(await readCandidateStartupDiagnostic(capability.rootId))?.startupAttemptId,
STARTUP_ATTEMPT_A,
);
assert.equal(
await readCandidateStartupDiagnostic(capability.rootId, STARTUP_ATTEMPT_B),
undefined,
);
});
});
test('accepts a ready successor launched before a migration blocker is observed', async () => {
await withHostPaths(async (paths) => {
let launches = 0;
let reportBlocker:
| ((failure: {
reason: 'operational_state_migration_blocked';
startupAttemptId: string;
}) => void)
| undefined;
const result = await connectOrSpawnRuntimeHostWithDependencies(
{
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
compositionId: KERNEL_COMPOSITION.descriptor.id,
candidateEntrypoint: KERNEL_CANDIDATE_ENTRYPOINT,
electionDeadlineMs: 5_000,
},
{
random: () => 0.5,
launchCandidate: (input) => {
launches += 1;
if (launches === 1) {
return {
spawned: Promise.resolve({
pid: process.pid,
exited: Promise.resolve({
code: 2,
signal: null,
stderr: '',
stderrTruncated: false,
}),
startupFailure: new Promise((resolve) => {
reportBlocker = resolve;
}),
}),
};
}
reportBlocker?.({
reason: 'operational_state_migration_blocked',
startupAttemptId: STARTUP_ATTEMPT_A,
});
return launchTestRuntimeHostCandidate(paths, {
...input,
});
},
},
);
assert.equal(result.kind, 'connected');
assert.ok(launches >= 2);
if (result.kind === 'connected') {
assert.equal(result.spawnedProcess?.pid, result.registration.pid);
await result.connection.close();
}
});
});
test('rejects a bound composition mismatch without launching a Candidate', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner);
if (!owner) return;
await bindStateRootComposition(owner.lease, 'maka.other');
await owner.close();
let launches = 0;
const result = await connectOrSpawnRuntimeHostWithDependencies(
{
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
compositionId: KERNEL_COMPOSITION.descriptor.id,
candidateEntrypoint: KERNEL_CANDIDATE_ENTRYPOINT,
electionDeadlineMs: 100,
},
{
random: () => 0.5,
launchCandidate: () => {
launches += 1;
return { spawned: Promise.resolve({ pid: process.pid }) };
},
},
);
assert.deepEqual(result, {
kind: 'failed',
reason: 'composition_mismatch',
requiredCompositionId: 'maka.other',
});
assert.equal(launches, 0);
});
});
test('service lifecycle remains ready until explicitly closed', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner);
const host = await RuntimeHostKernel.start({
owner,
lifecycleMode: 'service',
composition: KERNEL_COMPOSITION,
});
await sleep(25);
assert.equal(host.state, 'ready');
assert.equal(await tryAcquireInteractiveRootOwner(capability), undefined);
const connected = await connectRuntimeHost({
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
generation: 'desktop-current',
});
assert.equal(connected.kind, 'connected');
if (connected.kind !== 'connected') return;
assert.equal(connected.registration.lifecycleMode, 'service');
await connected.connection.close();
const incompatible = await connectOrSpawnRuntimeHost({
...paths,
rootPath: paths.root,
protocol: LEGACY_PROTOCOL,
compositionId: KERNEL_COMPOSITION.descriptor.id,
candidateEntrypoint: KERNEL_CANDIDATE_ENTRYPOINT,
electionDeadlineMs: 500,
});
assert.equal(incompatible.kind, 'incompatible');
if (incompatible.kind === 'incompatible') {
assert.equal(incompatible.handshake.replacement, 'blocked_by_residency');
}
await host.close();
const successor = await tryAcquireInteractiveRootOwner(capability);
assert.ok(successor);
await successor.close();
});
});
test('elects one owner, serves status and diagnostics, and releases ownership after true-idle shutdown', async () => {
await withHostPaths(async (paths) => {
const winner = await startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
idleGraceMs: 250,
});
assert.equal(winner.kind, 'winner');
if (winner.kind !== 'winner') return;
// Connect before the loser assertion: a resident connection cancels the
// winner's idle timer, so the loser candidate's storage work cannot drain
// the Host before this bounded connection attempt establishes residency.
const connected = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(connected.kind, 'connected');
if (connected.kind !== 'connected') return;
assert.deepEqual(
await startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
}),
{ kind: 'loser' },
);
const statuses = await Promise.all([
connected.connection.status(),
connected.connection.status(),
]);
for (const status of statuses) {
assert.equal(status.hostEpoch, winner.host.hostEpoch);
assert.equal(status.state, 'ready');
assert.equal(status.connections, 1);
}
const diagnostics = await connected.connection.request('host.diagnostics.query', {});
assert.equal(diagnostics.hostEpoch, winner.host.hostEpoch);
assert.equal(diagnostics.state, 'ready');
assert.equal(diagnostics.pid, process.pid);
assert.equal(diagnostics.platform, process.platform);
assert.equal(diagnostics.protocolVersion, RUNTIME_HOST_PROTOCOL_VERSION);
assert.equal(diagnostics.compatibilityEpoch, RUNTIME_HOST_COMPATIBILITY_EPOCH);
assert.equal(diagnostics.upgradeBlockingActivity, false);
assert.ok(Array.isArray(diagnostics.logs));
await connected.connection.close();
await winner.host.closed;
const next = await startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
idleGraceMs: 0,
});
assert.equal(next.kind, 'winner');
if (next.kind === 'winner') await next.host.closed;
});
});
test('serves bootstrap operations during recovery and rejects ready-only operations', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner);
let releaseFactory = () => {};
let markFactoryEntered!: () => void;
const factoryEntered = new Promise<void>((resolve) => {
markFactoryEntered = resolve;
});
const factoryReleased = new Promise<void>((resolve) => {
releaseFactory = resolve;
});
let maintenanceStarts = 0;
const unavailable = async () =>
({
ok: false,
error: {
code: 'operation_unavailable',
message: 'not available in this composition',
},
}) as const;
const hostTask = RuntimeHostKernel.start({
owner,
idleGraceMs: 10_000,
composition: defineInteractiveRuntimeHostComposition(async () => {
markFactoryEntered();
await factoryReleased;
return {
handlers: {
...createUnavailableDomainOperationHandlers(),
'turn.start': unavailable,
'turn.query': unavailable,
'turn.stop': unavailable,
'turn.message.submit': unavailable,
'queue.retract': unavailable,
'turn.interrupt': unavailable,
'interaction.query': unavailable,
'interaction.answer': unavailable,
'subscription.open': unavailable,
'subscription.close': unavailable,
},
beginDrain() {},
async recover() {},
startMaintenance() {
const registration = JSON.parse(
readFileSync(join(owner.controlDirectory, RUNTIME_HOST_REGISTRATION_FILE), 'utf8'),
);
assert.equal(registration.state, 'ready');
maintenanceStarts += 1;
},
async close() {},
};
}),
});
let host: RuntimeHostKernel | undefined;
let transport: FramedTransport | undefined;
try {
await withTimeout(factoryEntered, 1_000, 'Runtime Host did not enter composition');
const registration = await readHostRegistration(owner.controlDirectory);
assert.ok(registration);
assert.equal(registration.state, 'recovering');
assert.equal(maintenanceStarts, 0);
transport = new FramedTransport(await openSocket(registration.endpoint));
await writeClientFrame(transport, {
kind: 'hello',
clientInstanceId: 'lifecycle-test',
protocolMin: CURRENT_PROTOCOL.min,
protocolMax: CURRENT_PROTOCOL.max,
compatibilityEpoch: RUNTIME_HOST_COMPATIBILITY_EPOCH,
compositionId: 'maka.interactive',
});
const handshake = decodeHostFrame(await transport.read(1_000));
assert.ok('kind' in handshake && handshake.kind === 'accepted');
await writeClientFrame(transport, {
requestId: 'status',
operation: 'host.status',
input: {},
});
const status = decodeHostFrame(await transport.read(1_000));
assert.ok(!('kind' in status) && status.operation === 'host.status' && status.ok);
if (!('kind' in status) && status.operation === 'host.status' && status.ok) {
assert.equal(status.result.state, 'recovering');
}
await writeClientFrame(transport, {
requestId: 'query',
operation: 'turn.query',
input: { sessionId: 'session', turnId: 'turn' },
});
const query = decodeHostFrame(await transport.read(1_000));
assert.ok(!('kind' in query) && query.operation === 'turn.query' && !query.ok);
if (!('kind' in query) && query.operation === 'turn.query' && !query.ok) {
assert.equal(query.error.code, 'host_not_ready');
}
} finally {
releaseFactory();
transport?.abort();
host = await hostTask.catch(() => undefined);
await host?.close().catch(() => undefined);
}
assert.equal(maintenanceStarts, 1);
});
});
test('requestDrain synchronously begins composition drain exactly once', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner);
let context: RuntimeHostCompositionContext | undefined;
let drainCalls = 0;
const host = await RuntimeHostKernel.start({
owner,
idleGraceMs: 10_000,
composition: defineInteractiveRuntimeHostComposition(async (value) => {
context = value;
return testComposition({
beginDrain: () => {
drainCalls += 1;
},
});
}),
});
context?.requestDrain();
assert.equal(drainCalls, 1);
context?.requestDrain();
assert.equal(drainCalls, 1);
await host.closed;
});
});
test('execution settlement can exclude environment resources without releasing Host ownership', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner);
let accounting!: ReturnType<RuntimeHostCompositionContext['acquireResidency']>;
let environment!: ReturnType<RuntimeHostCompositionContext['acquireResidency']>;
let settlement!: Promise<void>;
const host = await RuntimeHostKernel.start({
owner,
idleGraceMs: 10_000,
composition: defineInteractiveRuntimeHostComposition(async (context) => {
accounting = context.acquireResidency('usage-accounting');
environment = context.acquireResidency('runtime-resource');
settlement = context.waitForResidenciesExcept!('runtime-resource');
return testComposition();
}),
});
let settled = false;
void settlement.then(() => {
settled = true;
});
await Promise.resolve();
assert.equal(settled, false);
accounting.release();
await settlement;
assert.equal(settled, true);
environment.release();
await host.close();
});
});
test('local owner prepares an ephemeral Host upgrade against the exact Host Epoch', async () => {
await withHostPaths(async (paths) => {
let context: RuntimeHostCompositionContext | undefined;
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner);
if (!owner) return;
const host = await RuntimeHostKernel.start({
owner,
idleGraceMs: 10_000,
composition: defineInteractiveRuntimeHostComposition(async (value) => {
context = value;
return testComposition();
}),
});
const connected = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(connected.kind, 'connected');
if (connected.kind !== 'connected') return;
await assert.rejects(
connected.connection.request('host.upgrade.prepare', {
expectedHostEpoch: 'stale-host-epoch',
allowInterruptActiveTasks: false,
}),
(error: unknown) =>
error instanceof RuntimeHostOperationError && error.code === 'operation_conflict',
);
assert.equal(host.state, 'ready');
const activity = context?.acquireResidency('hosted-execution');
assert.ok(activity);
assert.deepEqual(
await connected.connection.request('host.upgrade.prepare', {
expectedHostEpoch: host.hostEpoch,
allowInterruptActiveTasks: false,
}),
{ kind: 'active_tasks' },
);
assert.equal(host.state, 'ready');
assert.deepEqual(
await connected.connection.request('host.upgrade.prepare', {
expectedHostEpoch: host.hostEpoch,
allowInterruptActiveTasks: true,
}),
{ kind: 'prepared', pid: process.pid },
);
activity?.release();
await host.closed;
const successor = await tryAcquireInteractiveRootOwner(capability);
assert.ok(successor);
await successor?.close();
});
});
for (const scenario of ['transfer', 'unproven_residency', 'seal_refused'] as const) {
test(`cooperative Host handoff ${scenario} requires exact residency proof`, async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner);
let detached = 0;
let cancelled = 0;
let release = () => {};
const host = await RuntimeHostKernel.start({
owner,
lifecycleMode: 'service',
composition: defineInteractiveRuntimeHostComposition(async (context) => {
const proven = context.acquireResidency('hosted-execution');
const unknown =
scenario === 'unproven_residency'
? context.acquireResidency('hosted-execution')
: undefined;
release = () => {
proven.release();
unknown?.release();
};
return {
...testComposition(),
prepareHandoff: async () => ({
seal: async () => scenario !== 'seal_refused',
residencies: async () => [proven],
detach: async () => {
detached += 1;
proven.release();
},
cancel: () => {
cancelled += 1;
},
}),
};
}),
});
const connected = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(connected.kind, 'connected');
if (connected.kind !== 'connected') return;
try {
const result = await connected.connection.request('host.upgrade.prepare', {
expectedHostEpoch: host.hostEpoch,
allowInterruptActiveTasks: false,
allowCooperativeHandoff: true,
});
assert.equal(result.kind, scenario === 'transfer' ? 'prepared' : 'active_tasks');
assert.equal(detached, scenario === 'transfer' ? 1 : 0);
assert.equal(cancelled, scenario === 'transfer' ? 0 : 1);
if (scenario !== 'transfer') {
assert.equal(host.state, 'ready');
assert.equal((await connected.connection.status()).state, 'ready');
}
} finally {
release();
await connected.connection.close();
await host.close();
}
});
});
}
test('disconnect while converging cancels handoff and reopens command admission', async () => {
await withHostPaths(async (paths) => {
const owner = await tryAcquireInteractiveRootOwner(
await resolveStorageRoot({ path: paths.root, kind: 'interactive' }),
);
assert.ok(owner);
const preparing = deferred<void>();
const cancelled = deferred<void>();
let release = () => {};
const host = await RuntimeHostKernel.start({
owner,
lifecycleMode: 'service',
composition: defineInteractiveRuntimeHostComposition(async (context) => {
const residency = context.acquireResidency('hosted-execution');
release = residency.release;
return {
...testComposition(),
prepareHandoff: async (_epoch, signal) => {
preparing.resolve();
await new Promise<void>((resolve) =>
signal.addEventListener(
'abort',
() => {
cancelled.resolve();
resolve();
},
{ once: true },
),
);
return undefined;
},
};
}),
});
const connected = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(connected.kind, 'connected');
if (connected.kind !== 'connected') return;
try {
const request = connected.connection
.request('host.upgrade.prepare', {
expectedHostEpoch: host.hostEpoch,
allowInterruptActiveTasks: false,
allowCooperativeHandoff: true,
})
.catch(() => undefined);
await withTimeout(preparing.promise, 2_000, 'Handoff did not begin');
await connected.connection.close();
await withTimeout(cancelled.promise, 2_000, 'Disconnect did not cancel handoff');
await request;
assert.equal(host.state, 'ready');
const retry = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(retry.kind, 'connected');
if (retry.kind === 'connected') await retry.connection.close();
} finally {
release();
await connected.connection.close();
await host.close();
}
});
});
test('local owner can prepare a managed service Host for retirement', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner);
const host = await RuntimeHostKernel.start({
owner,
lifecycleMode: 'service',
composition: KERNEL_COMPOSITION,
});
const connected = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(connected.kind, 'connected');
if (connected.kind !== 'connected') return;
assert.deepEqual(
await connected.connection.request('host.upgrade.prepare', {
expectedHostEpoch: host.hostEpoch,
allowInterruptActiveTasks: false,
}),
{ kind: 'prepared', pid: process.pid },
);
await host.closed;
assert.equal(host.shutdownReason, 'retirement');
const successor = await tryAcquireInteractiveRootOwner(capability);
assert.ok(successor);
await successor?.close();
});
});
test('safe retirement refuses a second client that connected after discovery', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner);
const host = await RuntimeHostKernel.start({
owner,
lifecycleMode: 'service',
composition: KERNEL_COMPOSITION,
});
const replacement = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(replacement.kind, 'connected');
if (replacement.kind !== 'connected') return;
const lateClient = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(lateClient.kind, 'connected');
if (lateClient.kind !== 'connected') return;
assert.deepEqual(
await replacement.connection.request('host.upgrade.prepare', {
expectedHostEpoch: host.hostEpoch,
allowInterruptActiveTasks: false,
}),
{ kind: 'active_tasks' },
);
assert.equal(host.state, 'ready');
assert.equal(
(await replacement.connection.request('host.diagnostics.query', {}))
.upgradeBlockingActivity,
true,
);
await lateClient.connection.close();
assert.deepEqual(
await replacement.connection.request('host.upgrade.prepare', {
expectedHostEpoch: host.hostEpoch,
allowInterruptActiveTasks: false,
}),
{ kind: 'prepared', pid: process.pid },
);
await host.closed;
assert.equal(host.shutdownReason, 'retirement');
});
});
test('closing with a retirement reason reports a retirement shutdown', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner);
const host = await RuntimeHostKernel.start({
owner,
lifecycleMode: 'service',
composition: KERNEL_COMPOSITION,
});
assert.equal(host.shutdownReason, undefined);
await host.close({ reason: 'retirement' });
assert.equal(host.shutdownReason, 'retirement');
});
});
test('an explicit generation takeover drains only the exact unobserved ephemeral Host', async () => {
await withHostPaths(async (paths) => {
const candidate = await startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
generation: 'desktop-old',
idleGraceMs: 10_000,
});
assert.equal(candidate.kind, 'winner');
if (candidate.kind !== 'winner') return;
const observer = await connectRuntimeHost({
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
});
assert.equal(observer.kind, 'connected');
if (observer.kind !== 'connected') return;
const blocked = await connectRuntimeHost({
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
generation: 'desktop-new',
takeoverHostEpoch: candidate.host.hostEpoch,
});
assert.equal(blocked.kind, 'upgrade_required');
if (blocked.kind === 'upgrade_required') {
assert.equal(blocked.restartable, false);
assert.equal(blocked.registration.generation, 'desktop-old');
assert.equal(blocked.handshake?.generation, 'desktop-old');
assert.equal(blocked.handshake?.activity?.connections, 1);
}
assert.equal(candidate.host.state, 'ready');
await observer.connection.close();
const restartable = await connectRuntimeHost({
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
generation: 'desktop-new',
});
assert.equal(restartable.kind, 'upgrade_required');
if (restartable.kind === 'upgrade_required') {
assert.equal(restartable.restartable, true);
}
const takeover = await connectRuntimeHost({
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
generation: 'desktop-new',
takeoverHostEpoch: candidate.host.hostEpoch,
});
assert.equal(takeover.kind, 'draining');
await candidate.host.closed;
const replacement = await startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
generation: 'desktop-new',
idleGraceMs: 10_000,
});
assert.equal(replacement.kind, 'winner');
if (replacement.kind !== 'winner') return;
const attached = await connectRuntimeHost({
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
generation: 'desktop-new',
});
assert.equal(attached.kind, 'connected');
if (attached.kind === 'connected') {
assert.equal((await attached.connection.status()).state, 'ready');
await attached.connection.close();
}
await replacement.host.close();
});
});
test('quit activity ignores idle scheduler retention but preserves active work protection', async () => {
await withHostPaths(async (paths) => {
const owner = await tryAcquireInteractiveRootOwner(
await resolveStorageRoot({ path: paths.root, kind: 'interactive' }),
);
assert.ok(owner);
let context!: RuntimeHostCompositionContext;
const host = await RuntimeHostKernel.start({
owner,
composition: defineInteractiveRuntimeHostComposition(async (value) => {
context = value;
const retained = ['daily-review', 'scheduled-task', 'goal'].map((label) =>
context.acquireResidency(label, 'idle'),
);
return testComposition({
beginDrain: () => retained.forEach((lease) => lease.release()),
});
}),
});
try {
const connected = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(connected.kind, 'connected');
if (connected.kind !== 'connected') return;
const diagnostics = () => connected.connection.request('host.diagnostics.query', {});
const idle = await diagnostics();
assert.equal(idle.activeResidencies, 3);
assert.equal(idle.upgradeBlockingActivity, false);
for (const label of ['daily-review', 'scheduled-task', 'goal', 'runtime-resource']) {
const active = context.acquireResidency(label);
try {
assert.equal((await diagnostics()).upgradeBlockingActivity, true, label);
assert.deepEqual(
await connected.connection.request('host.upgrade.prepare', {
expectedHostEpoch: host.hostEpoch,
allowInterruptActiveTasks: false,
}),
{ kind: 'active_tasks' },
);
} finally {
active.release();
}
assert.equal((await diagnostics()).upgradeBlockingActivity, false);
}
assert.deepEqual(
await connected.connection.request('host.upgrade.prepare', {
expectedHostEpoch: host.hostEpoch,
allowInterruptActiveTasks: false,
}),
{ kind: 'prepared', pid: process.pid },
);
await host.closed;
} finally {
await host.close();
}
});
});
test('idle process retention permits a fenced handoff but never claims natural exit', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner);
const host = await RuntimeHostKernel.start({
owner,
generation: 'desktop-old',
idleGraceMs: 10_000,
composition: defineInteractiveRuntimeHostComposition(async (context) => {
context.retainUntilProcessExit();
return testComposition();
}),
});
try {
const observed = await connectRuntimeHost({
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
generation: 'desktop-new',
});
assert.equal(observed.kind, 'upgrade_required');
if (observed.kind !== 'upgrade_required') return;
assert.equal(observed.restartable, true);
assert.equal(observed.handshake?.replacement, 'blocked_by_residency');
assert.equal(observed.handshake?.activity?.drainResidencies, 0);
assert.deepEqual(observed.handshake?.activity?.residencies, [
{ label: 'process-retention', count: 1 },
]);
const replaced = await connectRuntimeHost({
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
generation: 'desktop-new',
takeoverHostEpoch: host.hostEpoch,
});
assert.equal(replaced.kind, 'draining');
await host.closed;
assert.equal(host.shutdownReason, 'retirement');
} finally {
await host.close();
}
});
});
test('does not take over an idle-looking Host with a residency acquired after discovery', async () => {
await withHostPaths(async (paths) => {
let context: RuntimeHostCompositionContext | undefined;
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner);
if (!owner) return;
const host = await RuntimeHostKernel.start({
owner,
generation: 'desktop-old',
idleGraceMs: 10_000,
composition: defineInteractiveRuntimeHostComposition(async (value) => {
context = value;
return testComposition();
}),
});
const discovered = await connectRuntimeHost({
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
generation: 'desktop-new',
});
assert.equal(discovered.kind, 'upgrade_required');
if (discovered.kind !== 'upgrade_required') return;
assert.equal(discovered.restartable, true);
const residency = context?.acquireResidency('late-activity');
assert.ok(residency);
const takeover = await connectRuntimeHost({
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
generation: 'desktop-new',
takeoverHostEpoch: host.hostEpoch,
});
assert.equal(takeover.kind, 'upgrade_required');
if (takeover.kind === 'upgrade_required') {
assert.equal(takeover.restartable, false);
assert.equal(takeover.handshake?.activity?.residencies.length, 1);
}
assert.equal(host.state, 'ready');
residency?.release();
await host.close();
});
});
test('does not take over a generation-mismatched Host before it is ready', async () => {
await withHostPaths(async (paths) => {
let markRecoveryEntered!: () => void;
let releaseRecovery!: () => void;
const recoveryEntered = new Promise<void>((resolve) => {
markRecoveryEntered = resolve;
});
const recovery = new Promise<void>((resolve) => {
releaseRecovery = resolve;
});
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner);
if (!owner) return;
const hostPromise = RuntimeHostKernel.start({
owner,
generation: 'desktop-old',
idleGraceMs: 10_000,
composition: defineInteractiveRuntimeHostComposition(async () => ({
...testComposition(),
async recover() {
markRecoveryEntered();
await recovery;
},
})),
});
try {
await withTimeout(recoveryEntered, 5_000, 'Runtime Host did not enter recovery');
const takeover = await connectRuntimeHost({
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
generation: 'desktop-new',
});
assert.equal(takeover.kind, 'upgrade_required');
if (takeover.kind === 'upgrade_required') {
assert.equal(takeover.restartable, false);
assert.equal(takeover.handshake?.state, 'recovering');
}
} finally {
releaseRecovery();
const host = await hostPromise;
await host.close();
await sleep(100);
}
});
});
test('process-exit retention neither stalls the graceful close nor retains ownership', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner);
const host = await RuntimeHostKernel.start({
owner,
idleGraceMs: 10_000,
shutdownGraceMs: 10_000,
composition: defineInteractiveRuntimeHostComposition(async (context) => {
context.retainUntilProcessExit();
context.retainUntilProcessExit();
context.requestDrain();
return testComposition();
}),
});
try {
// The anti-idle marker is not work: the drain it accompanies closes
// gracefully, long before the shutdown deadline.
await withTimeout(
host.closed,
2_000,
'retained Host waited out its shutdown deadline instead of closing gracefully',
);
await assert.rejects(
() => openSocket(host.endpoint),
(error: unknown) =>
error instanceof Error &&
'code' in error &&
((error as NodeJS.ErrnoException).code === 'ENOENT' ||
(error as NodeJS.ErrnoException).code === 'ECONNREFUSED'),
);
const successor = await tryAcquireInteractiveRootOwner(capability);
assert.ok(successor, 'graceful close must release the State Root writer lease');
await successor?.close();
} finally {
await owner.close();
}
});
});
test('never-connected ephemeral candidate drains after the initial connection timeout despite a boot residency', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner);
let releaseBootWork: (() => void) | undefined;
const host = await RuntimeHostKernel.start({
owner,
initialConnectionTimeoutMs: 100,
idleGraceMs: 10_000,
composition: defineInteractiveRuntimeHostComposition(async (context) => {
const residency = context.acquireResidency('boot-work');
releaseBootWork = () => residency.release();
return testComposition({
beginDrain: () => releaseBootWork?.(),
});
}),
});
await withTimeout(
host.closed,
2_000,
'ephemeral candidate outlived its initial connection timeout',
);
const successor = await tryAcquireInteractiveRootOwner(capability);
assert.ok(successor);
await successor.close();
});
});
test('never-connected ephemeral candidate with a hung composition startup fails stop at the deadlines', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner);
const hostTask = RuntimeHostKernel.start({
owner,
initialConnectionTimeoutMs: 100,
idleGraceMs: 10_000,
shutdownGraceMs: 100,
composition: defineInteractiveRuntimeHostComposition(() => new Promise(() => {})),
});
try {
const error = await withTimeout(
hostTask.then(
() => assert.fail('Runtime Host startup unexpectedly succeeded'),
(startupError: unknown) => startupError,
),
2_000,
'hung composition startup was not bounded by the initial connection deadline',
);
assert.ok(error instanceof AggregateError);
assert.ok(
error.errors.some(
(candidate: unknown) => candidate instanceof RuntimeHostProcessTerminationRequiredError,
),
);
} finally {
await owner.close();
}
});
});
test('a silent handshake defers the never-connected drain instead of being drained under it', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner);
const host = await RuntimeHostKernel.start({
owner,
initialConnectionTimeoutMs: 500,
idleGraceMs: 10_000,
handshakeTimeoutMs: 2_000,
composition: defineInteractiveRuntimeHostComposition(async () => testComposition()),
});
const silent = await openSocket(host.endpoint);
try {
// Past the initial connection timeout but inside the handshake
// deferral window: the pending handshake must keep the Host alive.
await new Promise((resolve) => setTimeout(resolve, 1_000));
assert.equal(host.state, 'ready');
} finally {
silent.destroy();
}
// With the handshake gone, the deferred deadline drains the Host.
await withTimeout(
host.closed,
5_000,
'silently-handshaked ephemeral candidate never drained after the deferral',
);
const successor = await tryAcquireInteractiveRootOwner(capability);
assert.ok(successor);
await successor.close();
});
});
test('an in-flight handshake keeps an ephemeral Host alive past the idle deadline', async () => {
await withHostPaths(async (paths) => {
const candidate = await startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
idleGraceMs: 250,
initialConnectionTimeoutMs: 5_000,
handshakeTimeoutMs: 5_000,
});
assert.equal(candidate.kind, 'winner');
if (candidate.kind !== 'winner') return;
const host = candidate.host;
// The first accepted connection leaves and the idle timer arms; a
// handshake that begins now is the phase the idle timer used to be
// blind to.
const first = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(first.kind, 'connected');
if (first.kind !== 'connected') return;
await first.connection.close();
const silent = await openSocket(host.endpoint);
await new Promise((resolve) => setTimeout(resolve, 50));
try {
// Past the idle deadline with the handshake in flight: the Host must
// not drain under a connecting Client.
await new Promise((resolve) => setTimeout(resolve, 500));
assert.equal(host.state, 'ready');
} finally {
silent.destroy();
}
// Once the handshake settles, the idle timer re-arms and the Host exits.
await withTimeout(
host.closed,
5_000,
'ephemeral Host never idle-exited after the handshake settled',
);
});
});
test('a poisoned Host closes gracefully without waiting out the shutdown deadline', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner);
const host = await RuntimeHostKernel.start({
owner,
lifecycleMode: 'service',
shutdownGraceMs: 10_000,
composition: defineInteractiveRuntimeHostComposition(async (context) => {
// Mirror the poison/fatal path: the anti-idle marker must not stall
// the drain it accompanies.
context.retainUntilProcessExit();
context.requestDrain();
return {
handlers: createUnavailableDomainOperationHandlers(),
beginDrain() {},
async recover() {},
async close() {},
};
}),
});
await withTimeout(
host.closed,
2_000,
'poisoned Host waited out its shutdown deadline instead of closing gracefully',
);
});
});
test('drain requested before factory completion begins drain before recovery exactly once', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner);
const lifecycle: string[] = [];
let releaseFactory!: () => void;
let markFactorySuspended!: () => void;
const factorySuspended = new Promise<void>((resolve) => {
markFactorySuspended = resolve;
});
const factoryReleased = new Promise<void>((resolve) => {
releaseFactory = resolve;
});
let startSettled = false;
const hostTask = RuntimeHostKernel.start({
owner,
idleGraceMs: 10_000,
composition: defineInteractiveRuntimeHostComposition(async (context) => {
context.requestDrain();
markFactorySuspended();
await factoryReleased;
lifecycle.push('factory-return');
return testComposition({
beginDrain: () => lifecycle.push('begin-drain'),
startMaintenance: () => lifecycle.push('maintenance'),
recover: async () => {
lifecycle.push('recover');
},
close: async () => {
lifecycle.push('close');
},
});
}),
});
void hostTask.then(
() => {
startSettled = true;
},
() => {
startSettled = true;
},
);
await withTimeout(factorySuspended, 1_000, 'composition factory did not suspend');
assert.equal(startSettled, false);
assert.deepEqual(lifecycle, []);
assert.equal(await tryAcquireInteractiveRootOwner(capability), undefined);
releaseFactory();
const host = await withTimeout(hostTask, 1_000, 'Runtime Host startup did not settle');
await host.closed;
assert.deepEqual(lifecycle, ['factory-return', 'begin-drain', 'recover', 'close']);
assert.equal(lifecycle.filter((event) => event === 'begin-drain').length, 1);
});
});
test('startup failure preserves its cause when shutdown reaches the active deadline', {
timeout: 10_000,
}, async (t) => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner);
let releaseClose!: () => void;
let markCloseEntered!: () => void;
const closeEntered = new Promise<void>((resolve) => {
markCloseEntered = resolve;
});
const closeReleased = new Promise<void>((resolve) => {
releaseClose = resolve;
});
const lifecycle: string[] = [];
// Fake timers isolate the shutdownGraceMs deadline from real I/O jitter
// (registration writes, listener admission, storage-root binding) that
// #closeResources() performs before it ever calls composition.close().
// On a loaded runner that real work alone can exceed a real 100ms
// deadline, aborting shutdown via #assertShutdownCanContinue() before
// close() is entered at all — closeEntered then never resolves, and the
// test fails with "composition close did not begin" despite the kernel
// behaving correctly. Enabling mock timers only after `owner` is already
// acquired keeps the earlier real lock-acquisition path (which does poll
// via a real setTimeout in @maka/storage's file-update-lock) unaffected.
t.mock.timers.enable({ apis: ['setTimeout'] });
const hostTask = RuntimeHostKernel.start({
owner,
idleGraceMs: 10_000,
shutdownGraceMs: 100,
composition: defineInteractiveRuntimeHostComposition(async (context) => {
context.requestDrain();
return testComposition({
beginDrain: () => lifecycle.push('begin-drain'),
recover: async () => {
lifecycle.push('recover');
throw new Error('forced startup recovery failure');
},
close: async () => {
lifecycle.push('close');
markCloseEntered();
await closeReleased;
},
});
}),
});
const startupFailure = hostTask.then(
() => assert.fail('Runtime Host startup unexpectedly succeeded'),
(error: unknown) => error,
);
try {
// #3844 widened these budgets to tolerate real wall-clock jitter on a
// loaded runner (see #3840). Fake timers remove that jitter, so these
// withTimeout wrappers no longer tolerate anything real - they are
// deliberate redundancy, kept only so a genuine regression (close()
// never entered, or startupFailure never settling after the tick)
// fails fast with a named error instead of the generic message from
// the outer 10_000ms test timeout.
await withTimeout(closeEntered, 5_000, 'composition close did not begin');
// Deliberately fire the shutdown deadline now that close() is
// confirmed to be genuinely stuck on closeReleased - the exact
// scenario this test exists to exercise, reproduced deterministically
// instead of raced against wall-clock jitter.
t.mock.timers.tick(100);
// Let the promise chain settle after the synchronous timer callback
// (same pattern as gitoxide-helper-invocation-internal.test.ts).
await new Promise<void>((resolve) => setImmediate(resolve));
const error = await withTimeout(
startupFailure,
5_000,
'Runtime Host startup ignored its shutdown deadline',
);
assert.ok(error instanceof AggregateError);
assert.match(String(error.cause), /forced startup recovery failure/);
assert.ok(
error.errors.some(
(candidate: unknown) => candidate instanceof RuntimeHostProcessTerminationRequiredError,
),
);
assert.deepEqual(lifecycle, ['begin-drain', 'recover', 'close']);
assert.equal(await tryAcquireInteractiveRootOwner(capability), undefined);
} finally {
releaseClose();
await owner.close();
}
});
});
test('blocks incompatible replacement while resident and permits it only after true idle', async () => {
await withHostPaths(async (paths) => {
const candidate = await startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
idleGraceMs: 500,
});
assert.equal(candidate.kind, 'winner');
if (candidate.kind !== 'winner') return;
const resident = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(resident.kind, 'connected');
if (resident.kind !== 'connected') return;
const staleWhileResident = new FramedTransport(await openSocket(candidate.host.endpoint));
await writeRawLocalIpc(
staleWhileResident,
encodeLegacyProtocolFrame({
kind: 'hello',
clientInstanceId: 'stale-schema-resident',
protocolMin: CURRENT_PROTOCOL.min,
protocolMax: CURRENT_PROTOCOL.max,
compatibilityEpoch: 20,
compositionId: KERNEL_COMPOSITION.descriptor.id,
}),
);
assert.deepEqual(decodeHostFrame(await staleWhileResident.read(1_000)), {
kind: 'incompatible',
hostEpoch: candidate.host.hostEpoch,
protocolMin: CURRENT_PROTOCOL.min,
protocolMax: CURRENT_PROTOCOL.max,
compatibilityEpoch: RUNTIME_HOST_COMPATIBILITY_EPOCH,
compositionId: 'maka.interactive',
compositionRevision: KERNEL_COMPOSITION.descriptor.revision,
state: 'ready',
replacement: 'blocked_by_residency',
});
staleWhileResident.abort();
await staleWhileResident.closed;
const blockedWhileResident = new FramedTransport(await openSocket(candidate.host.endpoint));
await writeRawLocalIpc(
blockedWhileResident,
encodeLegacyProtocolFrame({
kind: 'hello',
clientInstanceId: 'blocked-legacy-resident',
protocolMin: LEGACY_PROTOCOL.min,
protocolMax: LEGACY_PROTOCOL.max,
}),
);
const blockedResponse = decodeHostFrame(await blockedWhileResident.read(1_000));
assert.ok('kind' in blockedResponse && blockedResponse.kind === 'incompatible');
if ('kind' in blockedResponse && blockedResponse.kind === 'incompatible') {
assert.equal(blockedResponse.replacement, 'blocked_by_residency');
}
blockedWhileResident.abort();
await blockedWhileResident.closed;
// The rejected handshake's teardown is asynchronous Host-side; let it
// settle so only the next probe's own handshake remains in flight.
await sleep(50);
await resident.connection.close();
const staleAtIdle = new FramedTransport(await openSocket(candidate.host.endpoint));
await writeRawLocalIpc(
staleAtIdle,
encodeLegacyProtocolFrame({
kind: 'hello',
clientInstanceId: 'stale-schema-idle',
protocolMin: CURRENT_PROTOCOL.min,
protocolMax: CURRENT_PROTOCOL.max,
}),
);
const staleIdleResponse = decodeHostFrame(await staleAtIdle.read(1_000));
assert.ok('kind' in staleIdleResponse && staleIdleResponse.kind === 'incompatible');
if ('kind' in staleIdleResponse && staleIdleResponse.kind === 'incompatible') {
assert.equal(staleIdleResponse.replacement, 'wait_for_idle_exit');
}
staleAtIdle.abort();
await staleAtIdle.closed;
const replaceable = await Promise.all([
connectRuntimeHost({
...paths,
rootPath: paths.root,
protocol: LEGACY_PROTOCOL,
}),
connectRuntimeHost({
...paths,
rootPath: paths.root,
protocol: LEGACY_PROTOCOL,
}),
]);
for (const result of replaceable) {
assert.equal(result.kind, 'incompatible');
if (result.kind === 'incompatible') {
assert.equal(result.handshake.replacement, 'wait_for_idle_exit');
}
}
await candidate.host.closed;
const replacement = await startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
idleGraceMs: 10_000,
});
assert.equal(replacement.kind, 'winner');
if (replacement.kind !== 'winner') return;
assert.notEqual(replacement.host.hostEpoch, candidate.host.hostEpoch);
const attached = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(attached.kind, 'connected');
if (attached.kind !== 'connected') return;
await attached.connection.close();
await replacement.host.close();
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await retryOwner(capability, paths);
assert.ok(owner);
await owner?.close();
});
});
test('two independent Clients with different cache environments attach to one cold-start Host', async () => {
await withHostPaths(async (paths) => {
const first = spawnConnectClient(paths, 'a');
const second = spawnConnectClient(paths, 'b');
const [firstConnected, secondConnected] = await Promise.all([
waitForConnectedClient(first),
waitForConnectedClient(second),
]);
for (const pid of [...firstConnected.candidatePids, ...secondConnected.candidatePids]) {
paths.resources.trackPid(pid);
}
assert.equal(firstConnected.hostEpoch, secondConnected.hostEpoch);
first.send('close');
second.send('close');
await Promise.all([
waitForSuccessfulExit(first, 'first connect Client'),
waitForSuccessfulExit(second, 'second connect Client'),
]);
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await retryOwner(capability, paths);
assert.ok(owner);
await owner?.close();
});
});
test('Node and Electron Candidates arbitrate in both directions and force-kill releases ownership', async () => {
const electronPath = require('electron') as string;
const runtimes = [
[{}, { executable: electronPath, env: { ELECTRON_RUN_AS_NODE: '1' } }],
[{ executable: electronPath, env: { ELECTRON_RUN_AS_NODE: '1' } }, {}],
] as const;
for (const [holderRuntime, contenderRuntime] of runtimes) {
await withHostPaths(async (paths) => {
let holderPid: number | undefined;
let contenderPid: number | undefined;
let successorPid: number | undefined;
try {
const holder = await spawnTestRuntimeHostCandidate(paths, {
...paths,
...holderRuntime,
rootPath: paths.root,
idleGraceMs: 10_000,
});
holderPid = holder.pid;
const connected = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(connected.kind, 'connected');
if (connected.kind !== 'connected') return;
assert.equal(connected.registration.pid, holderPid);
const previousEndpoint = connected.registration.endpoint;
const contender = await spawnTestRuntimeHostCandidate(paths, {
...paths,
...contenderRuntime,
rootPath: paths.root,
idleGraceMs: 10_000,
});
contenderPid = contender.pid;
await waitForProcessExit(contenderPid);
paths.resources.forgetPid(contenderPid);
contenderPid = undefined;
const stillConnected = await connectRuntimeHost({
...paths,
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
});
assert.equal(stillConnected.kind, 'connected');
if (stillConnected.kind !== 'connected') return;
assert.equal(stillConnected.connection.hostEpoch, connected.connection.hostEpoch);
await stillConnected.connection.close();
const previousEpoch = connected.connection.hostEpoch;
process.kill(holderPid, 'SIGKILL');
await connected.connection.closed;
await waitForProcessExit(holderPid);
paths.resources.forgetPid(holderPid);
holderPid = undefined;
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const { controlDirectory } = await prepareStorageRootControlDirectory(capability);
const staleRegistration = await readHostRegistration(controlDirectory);
assert.equal(staleRegistration?.hostEpoch, previousEpoch);
let staleLaunchAttempts = 0;
const staleDiscovery = await connectOrSpawnRuntimeHostWithDependencies(
{
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
compositionId: KERNEL_COMPOSITION.descriptor.id,
candidateEntrypoint: KERNEL_CANDIDATE_ENTRYPOINT,
electionDeadlineMs: 100,
},
{
random: () => 0.5,
launchCandidate: () => {
staleLaunchAttempts += 1;
return { spawned: Promise.resolve({ pid: process.pid }) };
},
},
);
assert.equal(staleDiscovery.kind, 'failed');
if (staleDiscovery.kind !== 'failed') return;
assert.equal(staleDiscovery.reason, 'startup_timeout');
assert.ok(staleDiscovery.diagnostic);
assert.equal(staleDiscovery.diagnostic.candidateLaunches, staleLaunchAttempts);
assert.ok(staleLaunchAttempts > 0);
const successor = await connectOrSpawnRuntimeHostWithDependencies(
{
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
compositionId: KERNEL_COMPOSITION.descriptor.id,
candidateEntrypoint: KERNEL_CANDIDATE_ENTRYPOINT,
electionDeadlineMs: 5_000,
},
{
random: Math.random,
launchCandidate: (input) => {
const launch = launchTestRuntimeHostCandidate(paths, {
...input,
...contenderRuntime,
idleGraceMs: 200,
});
return {
spawned: launch.spawned.then((attempt) => {
successorPid = attempt.pid;
return attempt;
}),
};
},
},
);
assert.equal(successor.kind, 'connected');
if (successor.kind !== 'connected') return;
assert.notEqual(successor.connection.hostEpoch, previousEpoch);
if (process.platform !== 'win32') {
await assertPathMissing(previousEndpoint);
await assertPathMissing(dirname(previousEndpoint));
}
await successor.connection.close();
const owner = await retryOwner(capability, paths);
assert.ok(owner);
await owner?.close();
if (successorPid !== undefined) {
await waitForProcessExit(successorPid);
paths.resources.forgetPid(successorPid);
}
successorPid = undefined;
} finally {
terminateProcess(successorPid);
terminateProcess(contenderPid);
terminateProcess(holderPid);
}
});
}
});
test('a detached Host survives the launcher process that created it', async () => {
await withHostPaths(async (paths) => {
const callerCwd = await mkdtemp(join(tmpdir(), 'maka-runtime-host-launcher-cwd-'));
try {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const launcher = paths.resources.trackChild(
fork(
new URL('./fixtures/detached-launcher.js', import.meta.url),
[paths.root, capability.rootId],
{ cwd: callerCwd, stdio: ['ignore', 'ignore', 'inherit', 'ipc'] },
),
);
const launchedPid = await waitForLaunch(launcher);
paths.resources.trackPid(launchedPid);
await waitForExit(launcher);
// The detached Host must not retain a caller directory that may be a
// package verifier, updater, or project-owned temporary workspace.
await rm(callerCwd, { recursive: true, force: true });
const connected = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(connected.kind, 'connected');
if (connected.kind !== 'connected') return;
assert.equal(connected.registration.pid, launchedPid);
process.kill(launchedPid, 'SIGKILL');
await connected.connection.closed;
await waitForProcessExit(launchedPid);
paths.resources.forgetPid(launchedPid);
} finally {
await rm(callerCwd, { recursive: true, force: true });
}
});
});
test('a launcher-owned detached Host exits when its launcher is killed', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const launcher = paths.resources.trackChild(
fork(
new URL('./fixtures/detached-launcher.js', import.meta.url),
[paths.root, capability.rootId, 'close-on-launcher-exit'],
{ stdio: ['ignore', 'ignore', 'inherit', 'ipc'] },
),
);
const launchedPid = paths.resources.trackPid(await waitForLaunch(launcher));
const connected = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(connected.kind, 'connected');
if (connected.kind !== 'connected') return;
assert.equal(connected.registration.pid, launchedPid);
launcher.kill('SIGKILL');
await waitForExit(launcher);
await withTimeout(
connected.connection.closed,
5_000,
'launcher-owned detached Host survived its launcher',
);
await waitForProcessExit(launchedPid);
paths.resources.forgetPid(launchedPid);
});
});
for (const ending of ['natural exit', 'crash'] as const) {
test(`an invocation-owned detached Host retires after launcher ${ending}`, async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const launcher = paths.resources.trackChild(
fork(
new URL('./fixtures/detached-launcher.js', import.meta.url),
[paths.root, capability.rootId, 'invocation-owned'],
{ stdio: ['ignore', 'ignore', 'inherit', 'ipc'] },
),
);
const launchedPid = paths.resources.trackPid(await waitForLaunch(launcher));
const connected = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(connected.kind, 'connected');
if (connected.kind !== 'connected') return;
assert.equal(connected.registration.pid, launchedPid);
if (ending === 'natural exit') launcher.send('exit-naturally');
else launcher.kill('SIGKILL');
await withTimeout(waitForExit(launcher), 5_000, 'Host guard kept its launcher alive');
if (ending === 'natural exit') assert.equal(launcher.exitCode, 0);
await withTimeout(connected.connection.closed, 5_000, 'Host survived its invocation');
await waitForProcessExit(launchedPid);
paths.resources.forgetPid(launchedPid);
});
});
}
test('an authority-supervised Candidate exits if its launch owner is killed', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const launchOwnerClientInstanceId = 'authority-launch-owner';
const launcher = paths.resources.trackChild(
fork(
new URL('./fixtures/owned-authority-launcher.js', import.meta.url),
[
paths.root,
capability.rootId,
join(paths.base, 'authority-lease-probe'),
launchOwnerClientInstanceId,
// The owner-loss exit bound below covers the gated recovery
// window, so this run pins startup behind the gated-recovery
// entry instead of leaving both the window and the kill's
// ordering to however scheduling resolves them.
'../../test-only/owned-candidate-gated-recovery-main.js',
],
{ stdio: ['ignore', 'ignore', 'inherit', 'ipc'] },
),
);
const launchedPid = paths.resources.trackPid(await waitForLaunch(launcher));
// The gated-recovery entry parks composition creation behind these
// markers under the same base directory; the stall marker proves the
// Candidate reached the gated window, and the release marker lets the
// test unblock it through a channel that survives the launcher.
const stallMarker = join(paths.base, 'authority-lease-probe.stalled');
const releaseMarker = join(paths.base, 'authority-lease-probe.release');
const connected = await retryConnect(paths, CURRENT_PROTOCOL, {
clientInstanceId: launchOwnerClientInstanceId,
});
assert.equal(connected.kind, 'connected');
if (connected.kind !== 'connected') return;
assert.equal(connected.registration.pid, launchedPid);
const ordinary = await connectRuntimeHost({
...paths,
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
clientInstanceId: 'ordinary-client-during-finalization',
});
assert.equal(ordinary.kind, 'draining');
// The owner-loss contract under test is recorded pre-bind: a
// launch-owner Client is admitted while the Host is still recovering,
// and the guard that closes the Host on owner loss binds only after
// startup returns. Kill timing alone cannot prove the loss was
// recorded pre-bind — a test-side pause longer than the candidate's
// startup would silently turn this into the post-bind scenario — so
// the gated-recovery entry holds startup behind a release file and
// marks the stall; waiting for that marker makes the kill land inside
// the gated window by construction rather than by luck.
const stallDeadline = Date.now() + 10_000;
while (!existsSync(stallMarker) && Date.now() < stallDeadline) {
await sleep(20);
}
assert.ok(existsSync(stallMarker), 'gated-recovery entry never reached its stall window');
launcher.kill('SIGKILL');
await waitForExit(launcher);
// The process is the only thing that reports the claim. A Client's
// `connection.closed` does not: it is that Client's own transport, and
// the Client aborts it after its liveness probe goes unanswered for two
// seconds. A Host that is merely busy therefore resolves it while still
// running, so it is used only as a post-exit consistency check below.
//
// Startup — composition creation and recovery included — runs after the
// release and is not bounded by the kernel's shutdown grace, so the exit
// budget must not start at the release. The entry's `onWon` marker is
// the explicit guard-bound boundary that starts it instead: the
// launch-owner guard has bound and the pre-bind recorded loss is being
// acted on, so everything the 20-second deadline covers (the
// `shutdownGraceMs` close plus margin — which sits below the launcher's
// 60 s idle grace, so it cannot be satisfied by a Candidate that merely
// went idle) happens after the marker.
//
// The race below keeps that boundary honest without breaking local
// Windows runs: there the Candidate can be terminated abruptly the
// moment its launcher dies — no JS exit event, so no bind and no marker
// — and `isProcessAlive` releasing the wait only records that platform
// limitation, while a Candidate still alive without a marker past the
// deadline is a failure. The assertion observes the real
// operating-system PID: the kernel resolving its `closed` promise does
// not by itself mean the OS process has exited, so only CI verdicts
// count as cross-platform evidence here.
writeFileSync(releaseMarker, String(Date.now()));
const boundMarker = join(paths.base, 'authority-lease-probe.bound');
const boundDeadline = Date.now() + 10_000;
while (
!existsSync(boundMarker) &&
isProcessAlive(launchedPid) &&
Date.now() < boundDeadline
) {
await sleep(20);
}
assert.ok(
existsSync(boundMarker) || !isProcessAlive(launchedPid),
'gated-recovery entry never reached its guard bind',
);
await waitForProcessExit(launchedPid, 20_000);
await withTimeout(
connected.connection.closed,
5_000,
'authority-supervised Candidate exited without closing its Client connection',
);
paths.resources.forgetPid(launchedPid);
});
});
test('a committed authority-supervised Candidate admits ordinary Clients after release', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const launchOwnerClientInstanceId = 'committed-authority-launch-owner';
const launcher = paths.resources.trackChild(
fork(
new URL('./fixtures/owned-authority-launcher.js', import.meta.url),
[
paths.root,
capability.rootId,
join(paths.base, 'committed-authority-lease-probe'),
launchOwnerClientInstanceId,
],
{ stdio: ['ignore', 'ignore', 'inherit', 'ipc'] },
),
);
const launchedPid = paths.resources.trackPid(await waitForLaunch(launcher));
const owner = await retryConnect(paths, CURRENT_PROTOCOL, {
clientInstanceId: launchOwnerClientInstanceId,
});
assert.equal(owner.kind, 'connected');
if (owner.kind !== 'connected') return;
const beforeCommit = await connectRuntimeHost({
...paths,
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
clientInstanceId: 'ordinary-client-before-commit',
});
assert.equal(beforeCommit.kind, 'draining');
launcher.send('release');
const ordinary = await retryConnect(paths, CURRENT_PROTOCOL, {
clientInstanceId: 'ordinary-client-after-commit',
});
assert.equal(ordinary.kind, 'connected');
if (ordinary.kind === 'connected') {
assert.equal(
(await ordinary.connection.request('host.diagnostics.query', {})).pid,
launchedPid,
);
await ordinary.connection.close();
}
await owner.connection.close();
launcher.kill('SIGKILL');
await waitForExit(launcher);
terminateProcess(launchedPid);
await waitForProcessExit(launchedPid);
paths.resources.forgetPid(launchedPid);
});
});
test('a detached Candidate survives writing stderr after its launcher exits', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const markerPath = join(paths.base, 'stderr-after-launcher-exit');
const launcher = paths.resources.trackChild(
fork(
new URL('./fixtures/detached-launcher.js', import.meta.url),
[paths.root, capability.rootId, markerPath],
{ stdio: ['ignore', 'ignore', 'inherit', 'ipc'] },
),
);
const launchedPid = paths.resources.trackPid(await waitForLaunch(launcher));
await waitForExit(launcher);
assert.equal(await waitForFileText(markerPath), 'alive');
assert.equal(isProcessAlive(launchedPid), true);
terminateProcess(launchedPid);
await waitForProcessExit(launchedPid);
paths.resources.forgetPid(launchedPid);
});
});
test('an Electron Client using the real Candidate launcher survives its parent process', async () => {
const electronPath = require('electron') as string;
await withHostPaths(async (paths) => {
const parent = paths.resources.trackChild(
fork(new URL('./fixtures/electron-connect-parent.js', import.meta.url), [paths.root], {
execPath: electronPath,
execArgv: [],
stdio: ['ignore', 'ignore', 'inherit', 'ipc'],
env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' },
}),
);
const launched = await waitForElectronParentLaunch(parent, (pid) =>
paths.resources.trackPid(pid),
);
await waitForSuccessfulExit(parent, 'Electron connect parent');
assert.deepEqual(launched.candidatePids, [launched.pid]);
const connected = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(connected.kind, 'connected');
if (connected.kind !== 'connected') return;
assert.equal(connected.connection.hostEpoch, launched.hostEpoch);
assert.equal(connected.registration.pid, launched.pid);
process.kill(launched.pid, 'SIGKILL');
await connected.connection.closed;
await waitForProcessExit(launched.pid);
paths.resources.forgetPid(launched.pid);
});
});
test('Electron Candidate cleanup owns a launch reported before its parent fails', async () => {
const electronPath = require('electron') as string;
let candidatePid: number | undefined;
await withHostPaths(async (paths) => {
const parent = paths.resources.trackChild(
fork(
new URL('./fixtures/electron-connect-parent.js', import.meta.url),
[paths.root, 'exit-after-candidate-launch'],
{
execPath: electronPath,
execArgv: [],
stdio: ['ignore', 'ignore', 'inherit', 'ipc'],
env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' },
},
),
);
await assert.rejects(
waitForElectronParentLaunch(parent, (pid) => {
candidatePid = paths.resources.trackPid(pid);
}),
/Electron connect parent exited before reporting its Host: 23/,
);
assert.ok(candidatePid);
});
assert.ok(candidatePid);
assert.equal(isProcessAlive(candidatePid), false);
});
test('slow domain work preserves multiplexed requests and retires only explicit deadlines', async () => {
await withHostPaths(async (paths) => {
let releaseAdmitted!: () => void;
const admittedGate = new Promise<void>((resolve) => {
releaseAdmitted = resolve;
});
let markAdmitted!: () => void;
const admittedEntered = new Promise<void>((resolve) => {
markAdmitted = resolve;
});
let releaseLate!: () => void;
const lateGate = new Promise<void>((resolve) => {
releaseLate = resolve;
});
let markLate!: () => void;
const lateEntered = new Promise<void>((resolve) => {
markLate = resolve;
});
let markLateHandled!: () => void;
const lateHandled = new Promise<void>((resolve) => {
markLateHandled = resolve;
});
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = paths.resources.trackCloseable(
await tryAcquireInteractiveRootOwner(capability),
);
assert.ok(owner);
if (!owner) return;
const host = paths.resources.trackCloseable(
await RuntimeHostKernel.start({
owner,
idleGraceMs: 10_000,
composition: defineInteractiveRuntimeHostComposition(async () => ({
...testComposition(),
handlers: {
...createUnavailableDomainOperationHandlers(),
'memory.mutate': async () => {
markAdmitted();
await admittedGate;
return {
ok: true,
result: { kind: 'rejected', reason: 'invalid_state' },
};
},
'goal.query': async ({ sessionId }) => {
if (sessionId === 'blocked-session') await admittedGate;
if (sessionId === 'late-session') {
markLate();
await lateGate;
markLateHandled();
}
return { ok: true, result: { sessionId, goal: null } };
},
},
})),
}),
);
// Injected liveness cadence: the admitted request below must stay
// pending across probe cycles measured in this unit, not the real 2s
// one. The probe callback makes the premise a fact rather than an
// assumption: if the injected cadence ever stopped taking effect, the
// crossing below would time out instead of vacuously passing.
const livenessIntervalMs = 100;
let livenessProbes = 0;
let markProbesCrossed!: () => void;
const probeWindowCrossed = new Promise<void>((resolve) => {
markProbesCrossed = resolve;
});
const connected = await retryConnect(paths, CURRENT_PROTOCOL, {
livenessIntervalMs,
onLivenessProbe: () => {
livenessProbes += 1;
if (livenessProbes >= 2) markProbesCrossed();
},
});
assert.equal(connected.kind, 'connected');
if (connected.kind !== 'connected') return;
try {
const admitted = connected.connection.request('memory.mutate', {
kind: 'replace_begin',
expectedRevision: `sha256:${'a'.repeat(64)}`,
totalBytes: 0,
contentSha256: `sha256:${'b'.repeat(64)}`,
});
await admittedEntered;
const laneWaiter = connected.connection.request('goal.query', {
sessionId: 'blocked-session',
});
assert.deepEqual(
await withTimeout(
connected.connection.request('goal.query', { sessionId: 'unrelated-session' }),
500,
'unrelated Runtime Host request waited behind admitted work',
),
{ sessionId: 'unrelated-session', goal: null },
);
// Hold the admitted request pending until two liveness probes have
// observably round-tripped: surviving them proves probes never retire
// a request that has no explicit deadline (#2392).
await withTimeout(
probeWindowCrossed,
5_000,
'liveness probes did not fire on the injected cadence',
);
releaseAdmitted();
assert.deepEqual(await admitted, { kind: 'rejected', reason: 'invalid_state' });
assert.deepEqual(await laneWaiter, { sessionId: 'blocked-session', goal: null });
const locallyTimed = connected.connection.request(
'goal.query',
{ sessionId: 'late-session' },
50,
);
// Claim the rejection before awaiting anything else. The deadline above
// is shorter than the gate below can take to open on a loaded machine,
// so attaching the handler later leaves a window where the timeout is
// an unhandled rejection and fails the test on timing alone.
const locallyTimedRejected = assert.rejects(
locallyTimed,
(error: unknown) =>
error instanceof RuntimeHostRequestInterruptedError &&
error.reason === 'timeout' &&
error.retryable &&
error.cause instanceof RuntimeHostTransportError &&
error.cause.code === 'read_timeout',
);
await lateEntered;
await locallyTimedRejected;
assert.equal(
(await connected.connection.status()).hostEpoch,
connected.connection.hostEpoch,
);
releaseLate();
await lateHandled;
await new Promise<void>((resolve) => setImmediate(resolve));
assert.deepEqual(
await connected.connection.request('goal.query', { sessionId: 'after-late-response' }),
{ sessionId: 'after-late-response', goal: null },
);
} finally {
releaseAdmitted();
releaseLate();
await connected.connection.close();
await host.close();
}
});
});
test('an automatic failed liveness check is connection-fatal and Client close stays local', {
skip: process.platform === 'win32',
}, async () => {
await withHostPaths(async (paths) => {
const attempt = await spawnTestRuntimeHostCandidate(paths, {
...paths,
rootPath: paths.root,
idleGraceMs: 10_000,
});
let stopped = false;
try {
const connected = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(connected.kind, 'connected');
if (connected.kind !== 'connected') return;
process.kill(attempt.pid, 'SIGSTOP');
stopped = true;
await waitForProcessStopped(attempt.pid);
const pending = connected.connection.request('goal.query', {
sessionId: 'stopped-host-session',
});
await withTimeout(
assert.rejects(
pending,
(error: unknown) =>
error instanceof RuntimeHostRequestInterruptedError &&
error.reason === 'connection_lost' &&
error.retryable &&
error.cause instanceof RuntimeHostTransportError &&
error.cause.code === 'read_timeout' &&
error.cause.message.includes('host.status'),
),
12_000,
'automatic Runtime Host liveness check did not reject pending work',
);
await withTimeout(
connected.connection.closed,
500,
'timed-out Runtime Host connection did not close',
);
await withTimeout(
connected.connection.close(),
500,
'closing an already failed connection did not settle',
);
process.kill(attempt.pid, 'SIGCONT');
stopped = false;
const reconnected = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(reconnected.kind, 'connected');
if (reconnected.kind !== 'connected') return;
assert.equal(
(await reconnected.connection.status()).hostEpoch,
connected.connection.hostEpoch,
);
process.kill(attempt.pid, 'SIGSTOP');
stopped = true;
await waitForProcessStopped(attempt.pid);
await withTimeout(
reconnected.connection.close(),
500,
'Client close waited for an unresponsive Host',
);
} finally {
if (stopped) process.kill(attempt.pid, 'SIGCONT');
terminateProcess(attempt.pid);
}
});
});
test('bounded election never steals a live owner with no endpoint', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = paths.resources.trackCloseable(
await tryAcquireInteractiveRootOwner(capability),
);
assert.ok(owner);
const result = await connectOrSpawnRuntimeHost({
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
compositionId: KERNEL_COMPOSITION.descriptor.id,
candidateEntrypoint: KERNEL_CANDIDATE_ENTRYPOINT,
electionDeadlineMs: 100,
});
assert.equal(result.kind, 'failed');
if (result.kind !== 'failed') return;
assert.equal(result.reason, 'startup_timeout');
assert.ok(result.diagnostic);
assert.equal(await tryAcquireInteractiveRootOwner(capability), undefined);
await owner?.close();
});
});
test('rejects a structural owner copy before Host startup can use its lifecycle fields', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = paths.resources.trackCloseable(
await tryAcquireInteractiveRootOwner(capability),
);
assert.ok(owner);
assert.equal(Object.isFrozen(owner), true);
const redirectedControlDirectory = join(paths.base, 'redirected-control');
let copiedCloseCalled = false;
const copiedOwner = {
...owner,
controlDirectory: redirectedControlDirectory,
close: async () => {
copiedCloseCalled = true;
},
};
await assert.rejects(
() => RuntimeHostKernel.start({ owner: copiedOwner, composition: KERNEL_COMPOSITION }),
(error: unknown) =>
error instanceof StorageRootAuthorityError && error.code === 'invalid_owner',
);
assert.equal(copiedCloseCalled, false);
await assertPathMissing(redirectedControlDirectory);
assert.equal(await tryAcquireInteractiveRootOwner(capability), undefined);
await owner.close();
const nextOwner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(nextOwner);
await nextOwner.close();
});
});
test('releases an authentic owner when live validation fails before Host startup', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = paths.resources.trackCloseable(
await tryAcquireInteractiveRootOwner(capability),
);
assert.ok(owner);
const movedRoot = join(paths.base, 'moved-before-host-start');
await rename(paths.root, movedRoot);
await mkdir(paths.root);
await assert.rejects(
() => RuntimeHostKernel.start({ owner, composition: KERNEL_COMPOSITION }),
(error: unknown) =>
error instanceof StorageRootAuthorityError && error.code === 'root_identity_changed',
);
assert.equal(owner.closed, true);
const movedCapability = await resolveStorageRoot({ path: movedRoot, kind: 'interactive' });
assert.equal(movedCapability.rootId, capability.rootId);
const nextOwner = await tryAcquireInteractiveRootOwner(movedCapability);
assert.ok(nextOwner);
await nextOwner.close();
});
});
test('bounded election does not launch a Candidate after handshake exhausts the deadline', {
skip: process.platform === 'win32',
}, async () => {
await withHostPaths(async (paths) => {
const attempt = await spawnTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
idleGraceMs: 10_000,
});
let stopped = false;
try {
const connected = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(connected.kind, 'connected');
if (connected.kind !== 'connected') return;
await connected.connection.close();
process.kill(attempt.pid, 'SIGSTOP');
stopped = true;
await waitForProcessStopped(attempt.pid);
let launchCount = 0;
const result = await withTimeout(
connectOrSpawnRuntimeHostWithDependencies(
{
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
compositionId: KERNEL_COMPOSITION.descriptor.id,
candidateEntrypoint: KERNEL_CANDIDATE_ENTRYPOINT,
electionDeadlineMs: 50,
handshakeTimeoutMs: 5_000,
},
{
random: () => 0.5,
launchCandidate: () => {
launchCount += 1;
return { spawned: Promise.resolve({ pid: process.pid }) };
},
},
),
1_000,
'election exceeded its total deadline',
);
assert.equal(result.kind, 'failed');
if (result.kind !== 'failed') return;
assert.equal(result.reason, 'host_unresponsive');
assert.ok(result.diagnostic);
assert.equal(result.diagnostic.deadlineMs, 50);
assert.ok(result.diagnostic.elapsedMs >= 40);
assert.equal(result.diagnostic.candidateLaunches, 0);
assert.equal(result.diagnostic.sawEndpointConnected, true);
assert.ok(result.diagnostic.observations.deadlineElapsed >= 1);
assert.equal(result.diagnostic.lastRegistration?.pid, attempt.pid);
assert.equal(launchCount, 0);
} finally {
if (stopped) process.kill(attempt.pid, 'SIGCONT');
terminateProcess(attempt.pid);
}
});
});
test('answers an admitted bootstrap with draining after shutdown commits', async () => {
await withHostPaths(async (paths) => {
const candidate = await startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
idleGraceMs: 10_000,
});
assert.equal(candidate.kind, 'winner');
if (candidate.kind !== 'winner') return;
const socket = await openSocket(candidate.host.endpoint);
const transport = new FramedTransport(socket);
await new Promise<void>((resolve) => setImmediate(resolve));
const closing = candidate.host.close();
await writeClientFrame(transport, {
kind: 'hello',
clientInstanceId: 'draining-client',
protocolMin: CURRENT_PROTOCOL.min,
protocolMax: CURRENT_PROTOCOL.max,
compatibilityEpoch: RUNTIME_HOST_COMPATIBILITY_EPOCH,
compositionId: 'maka.interactive',
});
const response = decodeHostFrame(await transport.read(2_000));
assert.deepEqual(response, {
kind: 'draining',
hostEpoch: candidate.host.hostEpoch,
compositionId: 'maka.interactive',
compositionRevision: KERNEL_COMPOSITION.descriptor.revision,
});
transport.abort();
await transport.closed;
await closing;
});
});
test('rejects a previous-epoch Client before admitting catalog commands', async () => {
await withHostPaths(async (paths) => {
const candidate = await startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
idleGraceMs: 10_000,
});
assert.equal(candidate.kind, 'winner');
if (candidate.kind !== 'winner') return;
const transport = new FramedTransport(await openSocket(candidate.host.endpoint));
try {
await writeClientFrame(transport, {
kind: 'hello',
clientInstanceId: 'previous-epoch-client',
protocolMin: CURRENT_PROTOCOL.min,
protocolMax: CURRENT_PROTOCOL.max,
compatibilityEpoch: RUNTIME_HOST_COMPATIBILITY_EPOCH - 1,
compositionId: 'maka.interactive',
});
const response = decodeHostFrame(await transport.read(2_000));
assert.ok('kind' in response && response.kind === 'incompatible');
if (!('kind' in response) || response.kind !== 'incompatible') return;
assert.equal(response.compatibilityEpoch, RUNTIME_HOST_COMPATIBILITY_EPOCH);
assert.equal(response.hostEpoch, candidate.host.hostEpoch);
await transport.closed;
await assert.rejects(
() =>
writeClientFrame(transport, {
requestId: 'post-epoch-mismatch-catalog-query',
operation: 'connection.catalog.query',
input: { kind: 'start' },
}),
(error: unknown) => error instanceof RuntimeHostTransportError && error.code === 'closed',
);
} finally {
transport.abort();
}
});
});
test('accepts Client hellos with and without the legacy surface identity', async () => {
await withHostPaths(async (paths) => {
const candidate = await startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
idleGraceMs: 10_000,
});
assert.equal(candidate.kind, 'winner');
if (candidate.kind !== 'winner') return;
try {
for (const hello of [
{
kind: 'hello',
clientInstanceId: 'client-without-surface',
protocolMin: CURRENT_PROTOCOL.min,
protocolMax: CURRENT_PROTOCOL.max,
compatibilityEpoch: RUNTIME_HOST_COMPATIBILITY_EPOCH,
compositionId: 'maka.interactive',
},
{
kind: 'hello',
clientInstanceId: 'legacy-client-with-surface',
surface: 'tui',
protocolMin: CURRENT_PROTOCOL.min,
protocolMax: CURRENT_PROTOCOL.max,
compatibilityEpoch: RUNTIME_HOST_COMPATIBILITY_EPOCH,
compositionId: 'maka.interactive',
},
]) {
const transport = new FramedTransport(await openSocket(candidate.host.endpoint));
try {
await writeRawLocalIpc(transport, encodeLegacyProtocolFrame(hello));
const response = decodeHostFrame(await transport.read(2_000));
assert.ok('kind' in response && response.kind === 'accepted');
} finally {
transport.abort();
}
}
} finally {
await candidate.host.close();
}
});
});
test('releases composition connection resources after admitted requests settle', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner);
if (!owner) return;
let markHandlerEntered!: (connectionId: string) => void;
const handlerEntered = new Promise<string>((resolve) => {
markHandlerEntered = resolve;
});
let releaseHandler!: () => void;
const handlerReleased = new Promise<void>((resolve) => {
releaseHandler = resolve;
});
let markConnectionReleased!: (connectionId: string) => void;
const connectionReleased = new Promise<string>((resolve) => {
markConnectionReleased = resolve;
});
const releasedConnectionIds: string[] = [];
const host = await RuntimeHostKernel.start({
owner,
idleGraceMs: 10_000,
composition: defineInteractiveRuntimeHostComposition(async () => ({
handlers: {
...createUnavailableDomainOperationHandlers(),
'memory.mutate': async (_input, context) => {
markHandlerEntered(context.connectionId);
await handlerReleased;
return {
ok: true,
result: { kind: 'rejected', reason: 'invalid_state' },
};
},
},
releaseConnection(connectionId) {
releasedConnectionIds.push(connectionId);
markConnectionReleased(connectionId);
},
beginDrain() {},
async recover() {},
async close() {},
})),
});
const transport = new FramedTransport(await openSocket(host.endpoint));
try {
await writeClientFrame(transport, {
kind: 'hello',
clientInstanceId: 'composition-connection-release',
protocolMin: CURRENT_PROTOCOL.min,
protocolMax: CURRENT_PROTOCOL.max,
compatibilityEpoch: RUNTIME_HOST_COMPATIBILITY_EPOCH,
compositionId: 'maka.interactive',
});
const handshake = decodeHostFrame(await transport.read(2_000));
assert.ok('kind' in handshake && handshake.kind === 'accepted');
if (!('kind' in handshake) || handshake.kind !== 'accepted') return;
await writeClientFrame(transport, {
requestId: 'blocked-memory-mutation',
operation: 'memory.mutate',
input: {
kind: 'replace_begin',
expectedRevision: `sha256:${'a'.repeat(64)}`,
totalBytes: 0,
contentSha256: `sha256:${'b'.repeat(64)}`,
},
});
const admittedConnectionId = await handlerEntered;
assert.equal(admittedConnectionId, handshake.connectionId);
transport.abort();
await transport.closed;
await new Promise<void>((resolve) => setImmediate(resolve));
assert.deepEqual(releasedConnectionIds, []);
releaseHandler();
assert.equal(await connectionReleased, handshake.connectionId);
} finally {
releaseHandler();
transport.abort();
await host.close().catch(() => undefined);
}
});
});
test('delivers canonical authority changes to a Client admitted during recovery', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner);
if (!owner) return;
const hostChanges = new HostChangeFeed();
let releaseFactory!: () => void;
let markFactoryEntered!: () => void;
const factoryEntered = new Promise<void>((resolve) => {
markFactoryEntered = resolve;
});
const factoryReleased = new Promise<void>((resolve) => {
releaseFactory = resolve;
});
const hostTask = RuntimeHostKernel.start({
owner,
idleGraceMs: 10_000,
composition: defineInteractiveRuntimeHostComposition(async () => {
markFactoryEntered();
await factoryReleased;
return {
handlers: createUnavailableDomainOperationHandlers(),
hostChanges,
beginDrain() {},
async recover() {},
async close() {},
};
}),
});
let host: RuntimeHostKernel | undefined;
let connection: RuntimeHostConnection | undefined;
try {
await withTimeout(factoryEntered, 1_000, 'Runtime Host did not enter composition');
const connected = await connectRuntimeHost({
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
});
assert.equal(connected.kind, 'connected');
if (connected.kind !== 'connected') return;
const activeConnection = connected.connection;
connection = activeConnection;
const observed = new Promise<number>((resolve) => {
activeConnection.subscribeConfigurationChanges(resolve);
});
const observedCatalog = new Promise<string>((resolve) => {
activeConnection.subscribeSessionCatalogChanges(({ sessionId }) => resolve(sessionId));
});
releaseFactory();
host = await hostTask;
hostChanges.publishConfiguration();
hostChanges.publishSessionCatalog('session-1');
assert.equal(
await withTimeout(observed, 1_000, 'Client did not receive configuration change'),
1,
);
assert.equal(
await withTimeout(
observedCatalog,
1_000,
'Client did not receive Session catalog change',
),
'session-1',
);
} finally {
releaseFactory();
await connection?.close();
host ??= await hostTask.catch(() => undefined);
await host?.close().catch(() => undefined);
}
});
});
test('shutdown releases ownership after bounded handling of accepted and incomplete Clients', async () => {
await withHostPaths(async (paths) => {
const candidate = await startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
idleGraceMs: 10_000,
});
assert.equal(candidate.kind, 'winner');
if (candidate.kind !== 'winner') return;
const transport = new FramedTransport(await openHalfOpenSocket(candidate.host.endpoint));
const incompleteSocket = await openHalfOpenSocket(candidate.host.endpoint);
try {
await writeClientFrame(transport, {
kind: 'hello',
clientInstanceId: 'half-open-client',
protocolMin: CURRENT_PROTOCOL.min,
protocolMax: CURRENT_PROTOCOL.max,
compatibilityEpoch: RUNTIME_HOST_COMPATIBILITY_EPOCH,
compositionId: 'maka.interactive',
});
const handshake = decodeHostFrame(await transport.read(2_000));
assert.ok('kind' in handshake);
assert.equal(handshake.kind, 'accepted');
incompleteSocket.write('{"kind":"hello"');
await new Promise<void>((resolve) => setImmediate(resolve));
await withTimeout(
candidate.host.close(),
2_000,
'Host shutdown did not bound incomplete Clients',
);
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await retryOwner(capability, paths);
assert.ok(owner);
await owner?.close();
} finally {
transport.abort();
incompleteSocket.destroy();
}
});
});
test('a non-reading Client overload is isolated to its connection', {
skip: process.platform === 'win32',
}, async () => {
await withHostPaths(async (paths) => {
const candidate = await startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
idleGraceMs: 10_000,
});
assert.equal(candidate.kind, 'winner');
if (candidate.kind !== 'winner') return;
const nonReadingSocket = await openNonReadingStatusSocket(candidate.host.endpoint);
const observer = await connectRuntimeHost({
...paths,
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
});
assert.equal(observer.kind, 'connected');
if (observer.kind !== 'connected') return;
try {
const nonReadingClosed = new Promise<void>((resolve) => {
nonReadingSocket.once('close', () => resolve());
});
for (let index = 0; index < 10_000 && !nonReadingSocket.destroyed; index += 8) {
const batch = Array.from({ length: 8 }, (_, offset) =>
JSON.stringify({
requestId: `non-reading-${index + offset}`,
operation: 'host.status',
input: {},
}),
).join('\n');
nonReadingSocket.write(`${batch}\n`);
await new Promise<void>((resolve) => setImmediate(resolve));
}
await withTimeout(
nonReadingClosed,
2_000,
'Runtime Host did not evict the overloaded non-reading Client',
);
assert.equal((await observer.connection.status(2_000)).state, 'ready');
await observer.connection.close();
await withTimeout(
candidate.host.close(),
2_500,
'Host shutdown remained blocked after eviction',
);
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await retryOwner(capability, paths);
assert.ok(owner);
await owner?.close();
} finally {
nonReadingSocket.destroy();
await observer.connection.close();
}
});
});
test('reports one shutdown failure through close and closed while releasing ownership', {
skip: process.platform === 'win32',
}, async () => {
await withHostPaths(async (paths) => {
const candidate = await startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
idleGraceMs: 10_000,
});
assert.equal(candidate.kind, 'winner');
if (candidate.kind !== 'winner') return;
await unlink(candidate.host.endpoint);
await mkdir(candidate.host.endpoint);
await Promise.all([
assert.rejects(candidate.host.close(), AggregateError),
assert.rejects(candidate.host.closed, AggregateError),
]);
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = await retryOwner(capability, paths);
assert.ok(owner);
await owner?.close();
});
});
test('forces an uncooperative command Host to exit before a successor acquires ownership', {
timeout: 10_000,
}, async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const child = paths.resources.trackChild(
fork(
new URL('./fixtures/uncooperative-host.js', import.meta.url),
[paths.root, capability.rootId, '2000'],
{ stdio: ['ignore', 'ignore', 'inherit', 'ipc'] },
),
);
let transport: FramedTransport | undefined;
try {
const ready = await waitForUncooperativeHostMessage(child, 'ready');
transport = new FramedTransport(await openSocket(ready.endpoint));
await writeClientFrame(transport, {
kind: 'hello',
clientInstanceId: 'bounded-shutdown-test',
protocolMin: CURRENT_PROTOCOL.min,
protocolMax: CURRENT_PROTOCOL.max,
compatibilityEpoch: RUNTIME_HOST_COMPATIBILITY_EPOCH,
compositionId: 'maka.interactive',
});
const handshake = decodeHostFrame(await transport.read(2_000));
assert.ok('kind' in handshake);
assert.equal(handshake.kind, 'accepted');
const blocked = waitForUncooperativeHostMessage(child, 'operation-blocked');
await writeClientFrame(transport, {
requestId: 'blocked-turn-start',
operation: 'turn.start',
input: {
sessionId: 'session',
turnId: 'turn',
content: { text: 'block forever' },
},
});
await blocked;
const shutdownRequested = waitForUncooperativeHostMessage(child, 'shutdown-requested');
child.send({ type: 'shutdown' });
await shutdownRequested;
await writeClientFrame(transport, {
requestId: 'post-drain-status',
operation: 'host.status',
input: {},
});
const rejectedOperation = decodeHostFrame(await transport.read(1_000));
assert.ok(!('kind' in rejectedOperation));
if (!('kind' in rejectedOperation)) {
assert.equal(rejectedOperation.requestId, 'post-drain-status');
assert.equal(rejectedOperation.operation, 'host.status');
assert.equal(rejectedOperation.ok, false);
if (!rejectedOperation.ok) assert.equal(rejectedOperation.error.code, 'host_draining');
}
const rejectedHandshakeTransport = new FramedTransport(await openSocket(ready.endpoint));
try {
await writeClientFrame(rejectedHandshakeTransport, {
kind: 'hello',
clientInstanceId: 'post-drain-client',
protocolMin: CURRENT_PROTOCOL.min,
protocolMax: CURRENT_PROTOCOL.max,
compatibilityEpoch: RUNTIME_HOST_COMPATIBILITY_EPOCH,
compositionId: 'maka.interactive',
});
assert.deepEqual(decodeHostFrame(await rejectedHandshakeTransport.read(1_000)), {
kind: 'draining',
hostEpoch: ready.hostEpoch,
compositionId: 'maka.interactive',
compositionRevision: KERNEL_COMPOSITION.descriptor.revision,
});
} finally {
rejectedHandshakeTransport.abort();
}
assert.equal(child.exitCode, null);
assert.equal(child.signalCode, null);
const contender = await tryAcquireInteractiveRootOwner(capability);
try {
assert.equal(contender, undefined);
} finally {
await contender?.close();
}
const exit = await withTimeout(
waitForChildExitResult(child),
5_000,
'uncooperative Runtime Host did not exit within its shutdown bound',
);
assert.deepEqual(exit, { code: 1, signal: null });
const successor = await startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
idleGraceMs: 10_000,
});
assert.equal(successor.kind, 'winner');
if (successor.kind !== 'winner') return;
assert.notEqual(successor.host.hostEpoch, ready.hostEpoch);
const connected = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(connected.kind, 'connected');
if (connected.kind !== 'connected') return;
const status = await connected.connection.status();
assert.equal(status.hostEpoch, successor.host.hostEpoch);
await connected.connection.close();
await successor.host.close();
} finally {
transport?.abort();
if (child.exitCode === null && child.signalCode === null) child.kill('SIGKILL');
await withTimeout(waitForExit(child), 1_000, 'uncooperative Host cleanup did not exit');
}
});
});
test('startup rejects invalid lifecycle durations and releases the owner lock', async () => {
await withHostPaths(async (paths) => {
await assert.rejects(
() =>
startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
initialConnectionTimeoutMs: -1,
}),
RangeError,
);
await assert.rejects(
() =>
startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
idleGraceMs: -1,
}),
RangeError,
);
await assert.rejects(
() =>
startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
handshakeTimeoutMs: 0,
}),
RangeError,
);
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const owner = paths.resources.trackCloseable(
await tryAcquireInteractiveRootOwner(capability),
);
assert.ok(owner);
if (!owner) return;
await assert.rejects(
() =>
RuntimeHostKernel.start({
owner,
shutdownGraceMs: 0,
composition: KERNEL_COMPOSITION,
}),
RangeError,
);
const retry = await startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
idleGraceMs: 0,
});
assert.equal(retry.kind, 'winner');
if (retry.kind === 'winner') await retry.host.closed;
});
});
test('rejects invalid Client configuration before root mutation or Host classification', async () => {
await withHostPaths(async (paths) => {
await assert.rejects(
() =>
connectRuntimeHost({
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
connectTimeoutMs: 0,
}),
RangeError,
);
await assert.rejects(
() =>
connectRuntimeHost({
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
handshakeTimeoutMs: 0,
}),
RangeError,
);
await assert.rejects(
() =>
connectRuntimeHost({
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
clientInstanceId: '',
}),
RuntimeHostProtocolError,
);
await assert.rejects(
() =>
connectOrSpawnRuntimeHost({
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
compositionId: 'Invalid Composition',
candidateEntrypoint: KERNEL_CANDIDATE_ENTRYPOINT,
}),
RuntimeHostProtocolError,
);
await assertPathMissing(paths.root);
const candidate = await startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
idleGraceMs: 10_000,
});
assert.equal(candidate.kind, 'winner');
if (candidate.kind !== 'winner') return;
await assert.rejects(
() =>
connectOrSpawnRuntimeHost({
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
compositionId: KERNEL_COMPOSITION.descriptor.id,
candidateEntrypoint: KERNEL_CANDIDATE_ENTRYPOINT,
clientInstanceId: 'x'.repeat(129),
electionDeadlineMs: 100,
}),
RuntimeHostProtocolError,
);
assert.equal(candidate.host.state, 'ready');
await candidate.host.close();
});
});
test('detached launcher reports an executable spawn failure to its caller', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
await assert.rejects(
() =>
launchDetachedRuntimeHostCandidate({
rootPath: paths.root,
expectedRootId: capability.rootId,
executable: join(paths.root, 'missing-node'),
entrypoint: KERNEL_CANDIDATE_ENTRYPOINT,
}).spawned,
(error: unknown) =>
error instanceof Error &&
'code' in error &&
(error as NodeJS.ErrnoException).code === 'ENOENT',
);
});
});
test('detached launcher exposes exact Candidate process settlement', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const attempt = await launchDetachedRuntimeHostCandidate({
rootPath: paths.root,
expectedRootId: capability.rootId,
entrypoint: new URL('./fixtures/owned-candidate-exit.js', import.meta.url),
env: { MAKA_TEST_EXIT_CODE: '1' },
}).spawned;
assert.ok(attempt.exited);
assert.deepEqual(
await withTimeout(attempt.exited, 2_000, 'detached Candidate did not exit'),
{ code: 1, signal: null, stderr: '', stderrTruncated: false },
);
});
});
test('detached launcher preserves a bounded stderr tail with process exit evidence', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const attempt = await launchDetachedRuntimeHostCandidate({
rootPath: paths.root,
expectedRootId: capability.rootId,
entrypoint: new URL('./fixtures/candidate-stderr-exit.js', import.meta.url),
}).spawned;
paths.resources.trackPid(attempt.pid);
assert.ok(attempt.exited);
const exit = await withTimeout(attempt.exited, 2_000, 'Candidate exit was not observed');
paths.resources.forgetPid(attempt.pid);
assert.equal(exit.code, 23);
assert.equal(exit.signal, null);
assert.equal(exit.stderrTruncated, true);
assert.ok(Buffer.byteLength(exit.stderr, 'utf8') <= 4 * 1024);
assert.match(exit.stderr, /token=fixture-secret/);
});
});
test('detached launcher does not mark an exact-limit stderr payload as truncated', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const attempt = await launchDetachedRuntimeHostCandidate({
rootPath: paths.root,
expectedRootId: capability.rootId,
entrypoint: new URL('./fixtures/candidate-stderr-exit.js', import.meta.url),
env: { MAKA_TEST_STDERR_EXACT_LIMIT: '1' },
}).spawned;
paths.resources.trackPid(attempt.pid);
assert.ok(attempt.exited);
const exit = await withTimeout(attempt.exited, 2_000, 'Candidate exit was not observed');
paths.resources.forgetPid(attempt.pid);
assert.equal(exit.code, 24);
assert.equal(exit.signal, null);
assert.equal(exit.stderrTruncated, false);
assert.equal(Buffer.byteLength(exit.stderr, 'utf8'), 4 * 1024);
});
});
test('Candidate refuses a replacement root without initializing or owning it', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
await rename(paths.root, join(paths.base, 'original-root'));
await mkdir(paths.root);
const attempt = await spawnTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
expectedRootId: capability.rootId,
idleGraceMs: 10_000,
});
await waitForProcessExit(attempt.pid, 2_000);
paths.resources.forgetPid(attempt.pid);
await assertPathMissing(join(paths.root, STORAGE_ROOT_MARKER_FILE));
const replacement = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
assert.notEqual(replacement.rootId, capability.rootId);
const owner = await tryAcquireInteractiveRootOwner(replacement);
assert.ok(owner);
await owner?.close();
});
});
test('invalid registration fails closed without following its endpoint', async () => {
await withHostPaths(async (paths) => {
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const { controlDirectory } = await prepareStorageRootControlDirectory(capability);
await writeFile(
join(controlDirectory, 'registration.json'),
'{"endpoint":"/tmp/not-authority"}\n',
{
mode: 0o600,
},
);
const result = await connectRuntimeHost({
...paths,
rootPath: paths.root,
protocol: CURRENT_PROTOCOL,
});
assert.deepEqual(result, { kind: 'unavailable', reason: 'invalid_registration' });
});
});
test('malformed and oversized bootstrap frames close only the offending connection', async () => {
await withHostPaths(async (paths) => {
const candidate = await startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
idleGraceMs: 10_000,
});
assert.equal(candidate.kind, 'winner');
if (candidate.kind !== 'winner') return;
await sendInvalidBootstrap(candidate.host.endpoint, Buffer.from('not-json\n'));
await sendInvalidBootstrap(
candidate.host.endpoint,
Buffer.alloc(RUNTIME_HOST_MAX_MESSAGE_BYTES + 1, 0x61),
);
const connected = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(connected.kind, 'connected');
if (connected.kind !== 'connected') return;
assert.equal((await connected.connection.status()).state, 'ready');
await connected.connection.close();
await candidate.host.close();
});
});
test('drains after the live storage root identity disappears and releases the moved root', async () => {
await withHostPaths(async (paths) => {
const candidate = await startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
idleGraceMs: 10_000,
});
assert.equal(candidate.kind, 'winner');
if (candidate.kind !== 'winner') return;
const connected = await retryConnect(paths, CURRENT_PROTOCOL);
assert.equal(connected.kind, 'connected');
if (connected.kind !== 'connected') return;
const movedRoot = join(paths.base, 'moved-root');
await rename(paths.root, movedRoot);
await assert.rejects(() => connected.connection.status());
await candidate.host.closed;
assert.equal(candidate.host.state, 'draining');
const replacement = await startTestRuntimeHostCandidate(paths, {
rootPath: movedRoot,
idleGraceMs: 10_000,
});
assert.equal(replacement.kind, 'winner');
if (replacement.kind === 'winner') await replacement.host.close();
});
});
test('publishes private POSIX endpoint and registration permissions', {
skip: process.platform === 'win32',
}, async () => {
await withHostPaths(async (paths) => {
const candidate = await startTestRuntimeHostCandidate(paths, {
rootPath: paths.root,
idleGraceMs: 10_000,
});
assert.equal(candidate.kind, 'winner');
if (candidate.kind !== 'winner') return;
const capability = await resolveStorageRoot({ path: paths.root, kind: 'interactive' });
const { controlDirectory } = await prepareStorageRootControlDirectory(capability);
const registration = await readHostRegistration(controlDirectory);
assert.ok(registration);
assert.equal((await stat(registration.endpoint)).mode & 0o077, 0);
assert.equal((await stat(dirname(registration.endpoint))).mode & 0o077, 0);
assert.equal((await stat(join(controlDirectory, 'registration.json'))).mode & 0o077, 0);
await candidate.host.close();
});
});
});
function testComposition(
overrides: Partial<
Pick<RuntimeHostComposition, 'beginDrain' | 'recover' | 'close' | 'startMaintenance'>
> = {},
): RuntimeHostComposition {
return {
handlers: createUnavailableDomainOperationHandlers(),
beginDrain() {},
async recover() {},
async close() {},
...overrides,
};
}
interface HostPaths {
base: string;
root: string;
resources: HostTestResources;
}
interface CloseableTestResource {
close(): Promise<void>;
}
class HostTestResources {
readonly #closeables = new Set<CloseableTestResource>();
readonly #children = new Set<ChildProcess>();
readonly #pids = new Set<number>();
trackCloseable<T extends CloseableTestResource | undefined>(resource: T): T {
if (resource) this.#closeables.add(resource);
return resource;
}
trackChild<T extends ChildProcess>(child: T): T {
this.#children.add(child);
return child;
}
trackPid(pid: number): number {
this.#pids.add(pid);
return pid;
}
forgetPid(pid: number): void {
this.#pids.delete(pid);
}
async close(): Promise<void> {
await Promise.allSettled([...this.#closeables].reverse().map((resource) => resource.close()));
for (const child of this.#children) {
if (child.exitCode !== null || child.signalCode !== null) continue;
const exited = waitForExit(child);
child.kill('SIGKILL');
await withTimeout(exited, 1_000, 'test launcher did not exit during cleanup').catch(
() => undefined,
);
}
for (const pid of this.#pids) {
if (!isProcessAlive(pid)) continue;
terminateProcess(pid);
await waitForProcessExit(pid, 1_000).catch(() => undefined);
}
}
}
async function withHostPaths(run: (paths: HostPaths) => Promise<void>): Promise<void> {
const base = await mkdtemp(join(tmpdir(), 'maka-runtime-host-'));
const resources = new HostTestResources();
const paths = {
base,
root: join(base, 'root'),
resources,
};
try {
await run(paths);
} finally {
await resources.close();
await removeControlDirectoriesForRootsUnder(base);
await chmod(base, 0o700).catch(() => undefined);
await rm(base, { recursive: true, force: true });
}
}
function spawnConnectClient(paths: HostPaths, environmentSuffix: string): ChildProcess {
const fakeHome = join(paths.base, `fake-home-${environmentSuffix}`);
return paths.resources.trackChild(
fork(new URL('./fixtures/connect-client.js', import.meta.url), [paths.root], {
stdio: ['ignore', 'ignore', 'inherit', 'ipc'],
env: {
...process.env,
HOME: fakeHome,
XDG_CACHE_HOME: join(fakeHome, 'cache'),
XDG_RUNTIME_DIR: join(fakeHome, 'runtime'),
LOCALAPPDATA: join(fakeHome, 'local-app-data'),
},
}),
);
}
function waitForConnectedClient(
child: ChildProcess,
): Promise<{ hostEpoch: string; candidatePids: number[] }> {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
cleanup();
reject(new Error('connect Client did not report readiness'));
}, 10_000);
const cleanup = () => {
clearTimeout(timer);
child.off('error', onError);
child.off('exit', onExit);
child.off('message', onMessage);
};
const onError = (error: Error) => {
cleanup();
reject(error);
};
const onExit = (code: number | null, signal: NodeJS.Signals | null) => {
cleanup();
reject(new Error(`connect Client exited before readiness: ${code ?? signal}`));
};
const onMessage = (message: unknown) => {
if (!isConnectedClientMessage(message)) return;
cleanup();
resolve({ hostEpoch: message.hostEpoch, candidatePids: message.candidatePids });
};
child.once('error', onError);
child.once('exit', onExit);
child.on('message', onMessage);
});
}
function isConnectedClientMessage(
value: unknown,
): value is { type: 'connected'; hostEpoch: string; candidatePids: number[] } {
if (!value || typeof value !== 'object') return false;
const message = value as Record<string, unknown>;
return (
message.type === 'connected' &&
typeof message.hostEpoch === 'string' &&
Array.isArray(message.candidatePids) &&
message.candidatePids.every((pid) => Number.isSafeInteger(pid) && pid > 0)
);
}
async function retryConnect(
paths: HostPaths,
protocol: { min: number; max: number },
options?: {
livenessIntervalMs?: number;
onLivenessProbe?: () => void;
clientInstanceId?: string;
},
) {
const deadline = Date.now() + 5_000;
let result = await connectRuntimeHost({
...paths,
...options,
rootPath: paths.root,
protocol,
});
while (result.kind !== 'connected' && Date.now() < deadline) {
await sleep(20);
result = await connectRuntimeHost({
...paths,
...options,
rootPath: paths.root,
protocol,
});
}
return result;
}
async function retryOwner(capability: StorageRootCapability<'interactive'>, paths: HostPaths) {
const deadline = Date.now() + 5_000;
while (Date.now() < deadline) {
const owner = await tryAcquireInteractiveRootOwner(capability);
if (owner) return paths.resources.trackCloseable(owner);
await sleep(20);
}
return undefined;
}
async function startTestRuntimeHostCandidate(
paths: HostPaths,
options: Omit<InteractiveRuntimeHostCandidateOptions, 'expectedRootId'> & {
expectedRootId?: string;
},
): Promise<InteractiveRuntimeHostCandidateResult> {
const expectedRootId =
options.expectedRootId ??
(await resolveStorageRoot({ path: options.rootPath, kind: 'interactive' })).rootId;
const result = await startInteractiveRuntimeHostCandidate(
{ ...options, expectedRootId },
() => KERNEL_COMPOSITION,
);
if (result.kind === 'winner') paths.resources.trackCloseable(result.host);
return result;
}
async function spawnTestRuntimeHostCandidate(
paths: HostPaths,
input: Omit<DetachedCandidateInput, 'expectedRootId' | 'entrypoint'> & {
expectedRootId?: string;
entrypoint?: string | URL;
},
): Promise<DetachedCandidateAttempt> {
const expectedRootId =
input.expectedRootId ??
(await resolveStorageRoot({ path: input.rootPath, kind: 'interactive' })).rootId;
return launchTestRuntimeHostCandidate(paths, {
...input,
expectedRootId,
entrypoint: input.entrypoint ?? KERNEL_CANDIDATE_ENTRYPOINT,
}).spawned;
}
function launchTestRuntimeHostCandidate(
paths: HostPaths,
input: DetachedCandidateInput,
): DetachedCandidateLaunch {
const launch = launchDetachedRuntimeHostCandidate(input);
return {
spawned: launch.spawned.then((attempt) => {
paths.resources.trackPid(attempt.pid);
return attempt;
}),
};
}
function waitForLaunch(child: ChildProcess): Promise<number> {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error('launcher did not report')), 5_000);
child.once('message', (message) => {
if (
!message ||
typeof message !== 'object' ||
(message as { type?: unknown }).type !== 'launched'
)
return;
clearTimeout(timer);
resolve((message as { pid: number }).pid);
});
child.once('error', reject);
child.once('exit', (code, signal) => {
if (code !== 0) reject(new Error(`launcher exited: ${code ?? signal}`));
});
});
}
function waitForElectronParentLaunch(
child: ChildProcess,
onCandidateLaunched: (pid: number) => void,
): Promise<{ hostEpoch: string; pid: number; candidatePids: number[] }> {
return new Promise((resolve, reject) => {
const candidatePids: number[] = [];
const timer = setTimeout(() => {
cleanup();
reject(new Error('Electron connect parent did not report its Host'));
}, 10_000);
const cleanup = () => {
clearTimeout(timer);
child.off('error', onError);
child.off('exit', onExit);
child.off('message', onMessage);
};
const onError = (error: Error) => {
cleanup();
reject(error);
};
const onExit = (code: number | null, signal: NodeJS.Signals | null) => {
cleanup();
reject(
new Error(`Electron connect parent exited before reporting its Host: ${code ?? signal}`),
);
};
const onMessage = (message: unknown) => {
if (isElectronCandidateLaunch(message)) {
candidatePids.push(message.pid);
onCandidateLaunched(message.pid);
return;
}
if (!isElectronParentLaunch(message)) return;
cleanup();
resolve({
hostEpoch: message.hostEpoch,
pid: message.pid,
candidatePids,
});
};
child.once('error', onError);
child.once('exit', onExit);
child.on('message', onMessage);
});
}
function isElectronCandidateLaunch(
value: unknown,
): value is { type: 'electron-candidate-launched'; pid: number } {
if (!value || typeof value !== 'object') return false;
const message = value as Record<string, unknown>;
return (
message.type === 'electron-candidate-launched' &&
Number.isSafeInteger(message.pid) &&
(message.pid as number) > 0
);
}
function isElectronParentLaunch(value: unknown): value is {
type: 'electron-parent-launched';
hostEpoch: string;
pid: number;
} {
if (!value || typeof value !== 'object') return false;
const message = value as Record<string, unknown>;
return (
message.type === 'electron-parent-launched' &&
typeof message.hostEpoch === 'string' &&
Number.isSafeInteger(message.pid) &&
(message.pid as number) > 0
);
}
type UncooperativeHostMessage =
| { type: 'ready'; hostEpoch: string; endpoint: string }
| { type: 'operation-blocked' }
| { type: 'shutdown-requested' };
function waitForUncooperativeHostMessage<T extends UncooperativeHostMessage['type']>(
child: ChildProcess,
type: T,
): Promise<Extract<UncooperativeHostMessage, { type: T }>> {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
cleanup();
reject(new Error(`uncooperative Host did not report ${type}`));
}, 5_000);
const cleanup = () => {
clearTimeout(timer);
child.off('error', onError);
child.off('exit', onExit);
child.off('message', onMessage);
};
const onError = (error: Error) => {
cleanup();
reject(error);
};
const onExit = (code: number | null, signal: NodeJS.Signals | null) => {
cleanup();
reject(new Error(`uncooperative Host exited before ${type}: ${code ?? signal}`));
};
const onMessage = (message: unknown) => {
if (!isUncooperativeHostMessage(message) || message.type !== type) return;
cleanup();
resolve(message as Extract<UncooperativeHostMessage, { type: T }>);
};
child.once('error', onError);
child.once('exit', onExit);
child.on('message', onMessage);
});
}
function isUncooperativeHostMessage(value: unknown): value is UncooperativeHostMessage {
if (!value || typeof value !== 'object') return false;
const message = value as Record<string, unknown>;
if (message.type === 'operation-blocked' || message.type === 'shutdown-requested') return true;
return (
message.type === 'ready' &&
typeof message.hostEpoch === 'string' &&
typeof message.endpoint === 'string'
);
}
function waitForChildExitResult(
child: ChildProcess,
): Promise<{ code: number | null; signal: NodeJS.Signals | null }> {
if (child.exitCode !== null || child.signalCode !== null) {
return Promise.resolve({ code: child.exitCode, signal: child.signalCode });
}
return new Promise((resolve, reject) => {
const cleanup = () => {
child.off('error', onError);
child.off('exit', onExit);
};
const onError = (error: Error) => {
cleanup();
reject(error);
};
const onExit = (code: number | null, signal: NodeJS.Signals | null) => {
cleanup();
resolve({ code, signal });
};
child.once('error', onError);
child.once('exit', onExit);
});
}
function waitForExit(child: ChildProcess): Promise<void> {
if (child.exitCode !== null || child.signalCode !== null) return Promise.resolve();
return new Promise((resolve) => child.once('exit', () => resolve()));
}
async function waitForFileText(path: string, timeoutMs = 5_000): Promise<string> {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const value = await readFile(path, 'utf8').catch(() => undefined);
if (value !== undefined) return value;
await sleep(20);
}
throw new Error(`File was not written before timeout: ${path}`);
}
function waitForSuccessfulExit(child: ChildProcess, label: string): Promise<void> {
if (child.exitCode !== null || child.signalCode !== null) {
return child.exitCode === 0
? Promise.resolve()
: Promise.reject(new Error(`${label} exited: ${child.exitCode ?? child.signalCode}`));
}
return new Promise((resolve, reject) =>
child.once('exit', (code, signal) => {
if (code === 0) resolve();
else reject(new Error(`${label} exited: ${code ?? signal}`));
}),
);
}
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function waitForProcessExit(pid: number, timeoutMs = 5_000): Promise<void> {
const deadline = Date.now() + timeoutMs;
while (isProcessAlive(pid) && Date.now() < deadline) await sleep(20);
if (isProcessAlive(pid)) throw new Error(`process ${pid} did not exit`);
}
async function waitForProcessStopped(pid: number, timeoutMs = 2_000): Promise<void> {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const { stdout } = await execFileAsync('ps', ['-o', 'state=', '-p', String(pid)]);
if (/^[Tt]/.test(stdout.trim())) return;
if (!isProcessAlive(pid)) throw new Error(`Process ${pid} exited before it stopped`);
await sleep(5);
}
throw new Error(`Process ${pid} did not enter a stopped state`);
}
function isProcessAlive(pid: number): boolean {
try {
process.kill(pid, 0);
return true;
} catch (error) {
if (
error instanceof Error &&
'code' in error &&
(error as NodeJS.ErrnoException).code === 'ESRCH'
) {
return false;
}
throw error;
}
}
function terminateProcess(pid: number | undefined): void {
if (pid === undefined || !isProcessAlive(pid)) return;
try {
process.kill(pid, 'SIGKILL');
} catch (error) {
if (
!(
error instanceof Error &&
'code' in error &&
(error as NodeJS.ErrnoException).code === 'ESRCH'
)
) {
throw error;
}
}
}
function openSocket(path: string): Promise<Socket> {
return new Promise((resolve, reject) => {
const socket = connect(path);
socket.once('connect', () => resolve(socket));
socket.once('error', reject);
});
}
function openHalfOpenSocket(path: string): Promise<Socket> {
return new Promise((resolve, reject) => {
const socket = new Socket({ allowHalfOpen: true });
socket.once('connect', () => resolve(socket));
socket.once('error', reject);
socket.connect(path);
});
}
async function openNonReadingStatusSocket(path: string): Promise<Socket> {
const socket = new Socket();
await new Promise<void>((resolve, reject) => {
socket.once('connect', resolve);
socket.once('error', reject);
socket.connect(path);
});
const handshake = await new Promise<ReturnType<typeof decodeHostFrame>>((resolve, reject) => {
let buffered = '';
const cleanup = () => {
socket.off('data', onData);
socket.off('error', onError);
};
const onError = (error: Error) => {
cleanup();
reject(error);
};
const onData = (chunk: Buffer) => {
buffered += chunk.toString('utf8');
const newline = buffered.indexOf('\n');
if (newline < 0) return;
cleanup();
socket.pause();
try {
resolve(decodeHostFrame(JSON.parse(buffered.slice(0, newline))));
} catch (error) {
reject(error);
}
};
socket.on('data', onData);
socket.on('error', onError);
socket.write(
`${JSON.stringify({
kind: 'hello',
clientInstanceId: 'non-reading-client',
protocolMin: CURRENT_PROTOCOL.min,
protocolMax: CURRENT_PROTOCOL.max,
compatibilityEpoch: RUNTIME_HOST_COMPATIBILITY_EPOCH,
compositionId: 'maka.interactive',
})}\n`,
);
});
assert.ok('kind' in handshake);
assert.equal(handshake.kind, 'accepted');
socket.on('error', () => undefined);
return socket;
}
async function sendInvalidBootstrap(path: string, payload: Buffer): Promise<void> {
const socket = await openSocket(path);
socket.on('error', () => undefined);
const closed = new Promise<void>((resolve) => socket.once('close', () => resolve()));
socket.write(payload);
await withTimeout(closed, 1_000, 'Runtime Host did not close an invalid bootstrap connection');
}
function encodeLegacyProtocolFrame(frame: unknown): Buffer {
return Buffer.from(`${JSON.stringify(frame)}\n`, 'utf8');
}
function writeRawLocalIpc(transport: FramedTransport, frame: Uint8Array): Promise<void> {
return new Promise((resolve, reject) => {
transport.socket.write(frame, (error) => (error ? reject(error) : resolve()));
});
}
function writeClientFrame(transport: FramedTransport, frame: ClientFrame): Promise<void> {
return transport.write(encodeProtocolMessage(frame));
}
async function removeControlDirectoriesForRootsUnder(base: string): Promise<void> {
const rootIds = new Set<string>();
await collectRootIds(base, rootIds);
await Promise.all(
[...rootIds].map(async (rootId) => {
await rm(join(resolveRootControlNamespace(), rootId), { recursive: true, force: true });
await removePosixEndpointDirectories(rootId);
}),
);
}
async function collectRootIds(directory: string, rootIds: Set<string>): Promise<void> {
const entries = await readdir(directory, { withFileTypes: true }).catch(() => []);
for (const entry of entries) {
if (!entry.isDirectory()) continue;
const path = join(directory, entry.name);
const marker = await readFile(join(path, STORAGE_ROOT_MARKER_FILE), 'utf8').catch(
() => undefined,
);
if (marker) {
try {
const rootId = (JSON.parse(marker) as { rootId?: unknown }).rootId;
if (typeof rootId === 'string' && /^[a-f0-9]{64}$/.test(rootId)) rootIds.add(rootId);
} catch {
// Invalid markers never reach the Runtime Host control namespace.
}
}
await collectRootIds(path, rootIds);
}
}
async function assertPathMissing(path: string): Promise<void> {
await assert.rejects(
() => lstat(path),
(error: unknown) =>
error instanceof Error &&
'code' in error &&
(error as NodeJS.ErrnoException).code === 'ENOENT',
);
}