fix(#792): guard fleet roster schema errors
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful

This commit is contained in:
Hermes Agent
2026-07-17 15:48:52 -05:00
parent 080f720dfe
commit e0b0bde5ad
3 changed files with 48 additions and 0 deletions

View File

@@ -214,6 +214,42 @@ describe('fleet roster configuration failures', () => {
'Fleet roster at /srv/mosaic/fleet/roster.yaml is invalid. Fix the file or run `mosaic fleet init --force`.',
);
});
it('reports a semantically invalid v1 roster as an actionable nonzero command error', async () => {
const home = await tempDir();
const rosterPath = join(home, 'fleet', 'roster.yaml');
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
try {
await mkdir(dirname(rosterPath), { recursive: true });
await writeFile(
rosterPath,
[
'version: 1',
'transport: tmux',
'agents:',
' - name: canary-pi',
' runtime: pi',
' - name: canary-pi',
' runtime: codex',
].join('\n'),
);
await expect(
buildProgram().parseAsync(['node', 'mosaic', 'fleet', '--mosaic-home', home, 'ps']),
).rejects.toMatchObject({
code: 'fleet.roster',
exitCode: 1,
message: 'Fleet roster has duplicate agent name: canary-pi.',
});
expect(stderrSpy.mock.calls.flat().join('')).toContain(
'Fleet roster has duplicate agent name: canary-pi.',
);
expect(stderrSpy.mock.calls.flat().join('')).not.toMatch(/\n\s+at\s/);
} finally {
stderrSpy.mockRestore();
await rm(home, { recursive: true, force: true });
}
});
});
describe('fleet roster parsing', () => {

View File

@@ -197,6 +197,16 @@ export function getRosterAgent(roster: FleetRoster, name: string): FleetAgent {
}
export function normalizeFleetRosterV1(raw: RawFleetRoster): FleetRoster {
try {
return normalizeFleetRosterV1Unchecked(raw);
} catch (error) {
if (error instanceof FleetRosterConfigurationError) throw error;
if (error instanceof Error) throw new FleetRosterConfigurationError(error.message);
throw error;
}
}
function normalizeFleetRosterV1Unchecked(raw: RawFleetRoster): FleetRoster {
assertObject(raw, 'Fleet roster');
assertKnownKeys(raw, 'Fleet roster', [
'version',