fix(fleet): harden managed launch composition
AMD1213-C: repair stale array consumer, fail closed on foreign link provenance, validate manifests before mutation, and exercise the fleet MCP preflight call path.
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
||||
piForceSkillNames,
|
||||
registerRuntimeLaunchers,
|
||||
checkSequentialThinking,
|
||||
launchFleetRuntimeForTest,
|
||||
type RuntimeLaunchHandler,
|
||||
type ClaudexLaunchHandler,
|
||||
} from './launch.js';
|
||||
@@ -99,6 +100,95 @@ describe('registerRuntimeLaunchers — non-yolo subcommands', () => {
|
||||
});
|
||||
|
||||
describe('checkSequentialThinking', () => {
|
||||
it('runs the real fleet launch preflight against the injected seat, not HOME', () => {
|
||||
const home = mkdtempSync(join(tmpdir(), 'mosaic-seq-home-'));
|
||||
const agentDir = mkdtempSync(join(tmpdir(), 'mosaic-seq-seat-'));
|
||||
const installed = mkdtempSync(join(tmpdir(), 'mosaic-seq-installed-'));
|
||||
const checker = join(installed, 'tools', '_scripts', 'mosaic-ensure-sequential-thinking');
|
||||
try {
|
||||
expect(
|
||||
JSON.parse(
|
||||
readFileSync(
|
||||
join(process.cwd(), 'framework', 'runtime', 'claude', 'settings.json'),
|
||||
'utf8',
|
||||
),
|
||||
).mcpServers['sequential-thinking'],
|
||||
).toEqual({
|
||||
command: 'npx',
|
||||
args: ['-y', '@modelcontextprotocol/server-sequential-thinking'],
|
||||
});
|
||||
mkdirSync(join(installed, 'tools', '_scripts'), { recursive: true });
|
||||
copyFileSync(
|
||||
join(process.cwd(), 'framework', 'tools', '_scripts', 'mosaic-ensure-sequential-thinking'),
|
||||
checker,
|
||||
);
|
||||
mkdirSync(join(agentDir, '.claude'), { recursive: true });
|
||||
writeFileSync(
|
||||
join(agentDir, '.claude', '.claude.json'),
|
||||
JSON.stringify({
|
||||
mcpServers: {
|
||||
'sequential-thinking': {
|
||||
command: 'npx',
|
||||
args: ['-y', '@modelcontextprotocol/server-sequential-thinking'],
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ mode: 0o600 },
|
||||
);
|
||||
vi.stubEnv('HOME', home);
|
||||
const final = vi.fn((): never => {
|
||||
throw new Error('final runtime boundary');
|
||||
});
|
||||
expect(() =>
|
||||
launchFleetRuntimeForTest('claude', [], {}, { agentDir, mosaicHome: installed }, final),
|
||||
).toThrow('final runtime boundary');
|
||||
expect(final).toHaveBeenCalledOnce();
|
||||
} finally {
|
||||
vi.unstubAllEnvs();
|
||||
rmSync(home, { recursive: true, force: true });
|
||||
rmSync(agentDir, { recursive: true, force: true });
|
||||
rmSync(installed, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('fails the real fleet launch preflight when only operator HOME is seeded', () => {
|
||||
const home = mkdtempSync(join(tmpdir(), 'mosaic-seq-home-'));
|
||||
const agentDir = mkdtempSync(join(tmpdir(), 'mosaic-seq-seat-'));
|
||||
const installed = mkdtempSync(join(tmpdir(), 'mosaic-seq-installed-'));
|
||||
const checker = join(installed, 'tools', '_scripts', 'mosaic-ensure-sequential-thinking');
|
||||
const exit = vi.spyOn(process, 'exit').mockImplementation(exitThrows);
|
||||
try {
|
||||
mkdirSync(join(installed, 'tools', '_scripts'), { recursive: true });
|
||||
copyFileSync(
|
||||
join(process.cwd(), 'framework', 'tools', '_scripts', 'mosaic-ensure-sequential-thinking'),
|
||||
checker,
|
||||
);
|
||||
writeFileSync(
|
||||
join(home, '.claude.json'),
|
||||
JSON.stringify({
|
||||
mcpServers: {
|
||||
'sequential-thinking': {
|
||||
command: 'npx',
|
||||
args: ['-y', '@modelcontextprotocol/server-sequential-thinking'],
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
vi.stubEnv('HOME', home);
|
||||
expect(() =>
|
||||
launchFleetRuntimeForTest('claude', [], {}, { agentDir, mosaicHome: installed }, () => {
|
||||
throw new Error('must not execute');
|
||||
}),
|
||||
).toThrow('process.exit called');
|
||||
} finally {
|
||||
exit.mockRestore();
|
||||
vi.unstubAllEnvs();
|
||||
rmSync(home, { recursive: true, force: true });
|
||||
rmSync(agentDir, { recursive: true, force: true });
|
||||
rmSync(installed, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('passes with a seeded seat even when operator HOME has no MCP configuration', () => {
|
||||
const home = mkdtempSync(join(tmpdir(), 'mosaic-seq-home-'));
|
||||
const agentDir = mkdtempSync(join(tmpdir(), 'mosaic-seq-seat-'));
|
||||
@@ -121,6 +211,7 @@ describe('checkSequentialThinking', () => {
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ mode: 0o600 },
|
||||
);
|
||||
vi.stubEnv('MOSAIC_HOME', installed);
|
||||
vi.stubEnv('HOME', home);
|
||||
@@ -156,6 +247,7 @@ describe('checkSequentialThinking', () => {
|
||||
writeFileSync(
|
||||
join(agentDir, '.claude', '.claude.json'),
|
||||
JSON.stringify({ hasCompletedOnboarding: true, theme: 'dark' }),
|
||||
{ mode: 0o600 },
|
||||
);
|
||||
const env = { ...process.env, HOME: home, PATH: `${bin}:${process.env.PATH}` };
|
||||
expect(
|
||||
|
||||
Reference in New Issue
Block a user