fix(fleet): seed seat MCP preflight config

AMD1213-B5: derive Claude seat MCP configuration from the active installed runtime base and inspect the isolated seat during fleet launch.
This commit is contained in:
terra
2026-08-13 14:38:25 -05:00
parent fe2cf19461
commit 2755f86f7b
9 changed files with 326 additions and 22 deletions
+164 -1
View File
@@ -1,6 +1,17 @@
import { describe, it, expect, vi, beforeEach, afterEach, type MockInstance } from 'vitest';
import { Command } from 'commander';
import { mkdtempSync, mkdirSync, writeFileSync, symlinkSync, rmSync } from 'node:fs';
import { spawnSync } from 'node:child_process';
import {
chmodSync,
copyFileSync,
existsSync,
mkdtempSync,
mkdirSync,
readFileSync,
writeFileSync,
symlinkSync,
rmSync,
} from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import {
@@ -8,6 +19,7 @@ import {
enumerateSkillDirs,
piForceSkillNames,
registerRuntimeLaunchers,
checkSequentialThinking,
type RuntimeLaunchHandler,
type ClaudexLaunchHandler,
} from './launch.js';
@@ -86,6 +98,157 @@ describe('registerRuntimeLaunchers — non-yolo subcommands', () => {
});
});
describe('checkSequentialThinking', () => {
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-'));
const installed = mkdtempSync(join(tmpdir(), 'mosaic-seq-installed-'));
const checker = join(installed, 'tools', '_scripts', 'mosaic-ensure-sequential-thinking');
try {
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'],
},
},
}),
);
vi.stubEnv('MOSAIC_HOME', installed);
vi.stubEnv('HOME', home);
expect(() =>
checkSequentialThinking('claude', { agentDir, mosaicHome: installed }),
).not.toThrow();
} finally {
vi.unstubAllEnvs();
rmSync(home, { recursive: true, force: true });
rmSync(agentDir, { recursive: true, force: true });
rmSync(installed, { recursive: true, force: true });
}
});
it('repairs a legacy seat config in place without using operator 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');
const bin = join(installed, 'bin');
try {
mkdirSync(join(installed, 'tools', '_scripts'), { recursive: true });
mkdirSync(join(agentDir, '.claude'), { recursive: true });
mkdirSync(bin, { recursive: true });
copyFileSync(
join(process.cwd(), 'framework', 'tools', '_scripts', 'mosaic-ensure-sequential-thinking'),
checker,
);
for (const name of ['node', 'npx']) {
writeFileSync(join(bin, name), '#!/usr/bin/env bash\nexit 0\n');
chmodSync(join(bin, name), 0o755);
}
writeFileSync(
join(agentDir, '.claude', '.claude.json'),
JSON.stringify({ hasCompletedOnboarding: true, theme: 'dark' }),
);
const env = { ...process.env, HOME: home, PATH: `${bin}:${process.env.PATH}` };
expect(
spawnSync(
checker,
['--runtime', 'claude', '--claude-config-dir', join(agentDir, '.claude')],
{
env,
},
).status,
).toBe(0);
expect(
spawnSync(
checker,
['--check', '--runtime', 'claude', '--claude-config-dir', join(agentDir, '.claude')],
{ env },
).status,
).toBe(0);
expect(
JSON.parse(readFileSync(join(agentDir, '.claude', '.claude.json'), 'utf8')),
).toMatchObject({
hasCompletedOnboarding: true,
theme: 'dark',
mcpServers: { 'sequential-thinking': { command: 'npx' } },
});
expect(existsSync(join(home, '.claude.json'))).toBe(false);
} finally {
rmSync(home, { recursive: true, force: true });
rmSync(agentDir, { recursive: true, force: true });
rmSync(installed, { recursive: true, force: true });
}
});
it('rejects a group-writable installed helper root', () => {
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 {
mkdirSync(join(installed, 'tools', '_scripts'), { recursive: true });
copyFileSync(
join(process.cwd(), 'framework', 'tools', '_scripts', 'mosaic-ensure-sequential-thinking'),
checker,
);
chmodSync(installed, 0o770);
expect(() => checkSequentialThinking('claude', { agentDir, mosaicHome: installed })).toThrow(
/not a trusted installed file/,
);
} finally {
chmodSync(installed, 0o700);
rmSync(agentDir, { recursive: true, force: true });
rmSync(installed, { recursive: true, force: true });
}
});
it('refuses an empty seat even when operator HOME is configured', () => {
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('MOSAIC_HOME', installed);
vi.stubEnv('HOME', home);
expect(() => checkSequentialThinking('claude', { agentDir, mosaicHome: installed })).toThrow(
'process.exit called',
);
expect(exit).toHaveBeenCalledWith(1);
} finally {
exit.mockRestore();
vi.unstubAllEnvs();
rmSync(home, { recursive: true, force: true });
rmSync(agentDir, { recursive: true, force: true });
rmSync(installed, { recursive: true, force: true });
}
});
});
describe('buildPiSkillArgs', () => {
it('disables auto-discovery but force-loads fleet-critical skills by default', () => {
expect(buildPiSkillArgs([], {}, fakeSkills, fakeForced)).toEqual([