blob: d87c4ce41751a8588b596001763538a01151aa54 [file]
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { createHash, randomUUID } from 'node:crypto';
import { mkdir, mkdtemp, readFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import test from 'node:test';
import { resolveStorageRoot } from '@maka/storage/root-authority';
import {
readCandidateStartupDiagnostic,
resolveCandidateStartupDiagnosticPath,
} from '../control/startup-diagnostic.js';
const CANDIDATE_ENTRYPOINT = fileURLToPath(
new URL('../execution-candidate-main.js', import.meta.url),
);
const ROOT_ID = 'a'.repeat(64);
const STARTUP_ATTEMPT_ID = '00000000-0000-4000-8000-000000000001';
test('classifies invalid candidate arguments as an internal startup failure', () => {
const result = spawnSync(
process.execPath,
[
CANDIDATE_ENTRYPOINT,
'--root',
'/tmp/workspace',
'--expected-root-id',
ROOT_ID,
'--startup-attempt-id',
STARTUP_ATTEMPT_ID,
'--desktop-e2e',
'1',
],
{ encoding: 'utf8', timeout: 10_000 },
);
assert.equal(result.status, 70, result.stderr);
assert.match(result.stderr, /\[runtime-host\] startup failed:/);
assert.match(result.stderr, /Invalid Runtime Host candidate argument: --desktop-e2e/);
});
test('preserves a valid Candidate invocation failure across the detached stderr boundary', async () => {
const root = await mkdtemp(join(tmpdir(), 'maka-candidate-diagnostic-'));
const mismatchedRootId = createHash('sha256').update(randomUUID()).digest('hex');
const startupAttemptId = randomUUID();
const diagnosticPath = resolveCandidateStartupDiagnosticPath(mismatchedRootId, startupAttemptId);
const controlDirectory = dirname(diagnosticPath);
try {
await resolveStorageRoot({ path: root, kind: 'interactive' });
await mkdir(controlDirectory, { recursive: true, mode: 0o700 });
const result = spawnSync(
process.execPath,
[
CANDIDATE_ENTRYPOINT,
'--root',
root,
'--expected-root-id',
mismatchedRootId,
'--startup-attempt-id',
startupAttemptId,
],
{ encoding: 'utf8', timeout: 10_000 },
);
assert.equal(result.status, 70, result.stderr);
const diagnostic = await readCandidateStartupDiagnostic(mismatchedRootId, startupAttemptId);
assert.ok(diagnostic);
assert.equal(diagnostic.reason, 'internal_startup_failure');
assert.equal(diagnostic.startupAttemptId, startupAttemptId);
assert.ok(diagnostic.logs.every((entry) => !entry.includes('startup failed')));
assert.ok(diagnostic.errorChain.some((entry) => entry.code === 'root_identity_changed'));
} finally {
await rm(root, { recursive: true, force: true });
await rm(controlDirectory, { recursive: true, force: true });
}
});
/**
* Release packaging drops every `test-only/` module, so the production
* candidate entry must not be able to reach one — statically, not merely at
* runtime. Walk the built module graph across the bundled `@maka/*` packages
* and report every test-only module it can reach.
*/
test('the production candidate entry never reaches a test-only module', async () => {
const entry = new URL('../execution-candidate-main.js', import.meta.url).href;
const seen = new Set<string>([entry]);
const queue: string[] = [entry];
const reached: string[] = [];
while (queue.length > 0) {
const current = queue.pop();
if (current === undefined) break;
if (current.includes('/test-only/')) {
reached.push(current);
continue;
}
let source: string;
try {
source = await readFile(new URL(current), 'utf8');
} catch {
continue;
}
for (const specifier of staticImportSpecifiers(source)) {
const resolved = resolveModule(specifier, current);
if (resolved === undefined || seen.has(resolved)) continue;
seen.add(resolved);
queue.push(resolved);
}
}
assert.deepEqual(reached, []);
assert.ok(seen.size > 50, `module graph looks truncated: ${seen.size} modules`);
});
function resolveModule(specifier: string, parent: string): string | undefined {
try {
if (specifier.startsWith('.')) return new URL(specifier, parent).href;
if (specifier.startsWith('@maka/')) {
const resolved = import.meta.resolve(specifier);
return resolved.startsWith('file:') ? resolved : undefined;
}
} catch {
return undefined;
}
return undefined;
}
function staticImportSpecifiers(source: string): string[] {
const specifiers: string[] = [];
for (const match of source.matchAll(
/(?:^|[\s;}])(?:import|export)\b[^'"();]*?from\s*['"]([^'"]+)['"]/g,
)) {
if (match[1] !== undefined) specifiers.push(match[1]);
}
for (const match of source.matchAll(/(?:^|[\s;}])import\s*['"]([^'"]+)['"]/g)) {
if (match[1] !== undefined) specifiers.push(match[1]);
}
// Literal dynamic imports are real edges in the shipped graph — the built
// `dist` already contains several — so a walk that ignored them could pass
// while a production module reached test-only material through `import(…)`.
for (const match of source.matchAll(/\bimport\s*\(\s*['"]([^'"]+)['"]/g)) {
if (match[1] !== undefined) specifiers.push(match[1]);
}
return specifiers;
}