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

This commit is contained in:
Hermes Agent
2026-07-17 12:36:17 -05:00
parent 1d4a94d66f
commit 50276943cd
3 changed files with 20 additions and 2 deletions

View File

@@ -1,4 +1,4 @@
import { readFile, stat } from 'node:fs/promises';
import { stat } from 'node:fs/promises';
import { homedir } from 'node:os';
import { join, relative, resolve } from 'node:path';
import type { Command } from 'commander';
@@ -18,6 +18,7 @@ import {
validateRosterV2Semantics,
type FleetRosterV2,
} from '../fleet/roster-v2.js';
import { FleetRosterConfigurationError, readFleetRosterText } from '../fleet/fleet-roster-v1.js';
/**
* `mosaic fleet regen` — recovery-framed regeneration of the roster-derived
@@ -303,7 +304,7 @@ function defaultReadRoster(
mosaicHome: string,
): (rosterPath: string) => Promise<FleetRosterV2> {
return async (rosterPath: string): Promise<FleetRosterV2> => {
const roster = parseRosterV2(await readFile(rosterPath, 'utf8'), 'yaml');
const roster = parseRosterV2(await readFleetRosterText(rosterPath), 'yaml');
// Enforce the SAME semantic gate as reconcile/plan/verify (persona resolution
// + protected-class tool-policy match) so regen cannot project a roster the
// rest of the fleet surface would reject.
@@ -433,6 +434,10 @@ export function registerFleetRegenCommand(fleetCommand: Command, deps: FleetRege
// the operator must finish/verify (and clear the lock) before restarting.
if (result.incomplete || result.cleanup) process.exitCode = 1;
} catch (error: unknown) {
if (error instanceof FleetRosterConfigurationError) {
fleetCommand.error(error.message, { code: 'fleet.roster', exitCode: 1 });
return;
}
process.exitCode = 1;
const message = error instanceof Error ? error.message : String(error);
process.stderr.write(`mosaic fleet regen failed: ${message}\n`);