fix(fleet): resolve-then-validate symlink guard + framework helper resolution (#1380) (#1383)
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/push/publish Pipeline failed
Co-authored-by: code-be-01 <[email protected]>
This commit was merged in pull request #1383.
This commit is contained in:
@@ -7,6 +7,7 @@ import { canonicalizeRoleClass } from '../commands/fleet-personas.js';
|
||||
interface RawFleetRoster {
|
||||
version?: unknown;
|
||||
transport?: unknown;
|
||||
generation?: unknown;
|
||||
tmux?: {
|
||||
socket_name?: unknown;
|
||||
socketName?: unknown;
|
||||
@@ -41,6 +42,10 @@ interface RawFleetRoster {
|
||||
resetBetweenTasks?: unknown;
|
||||
kickstart_template?: unknown;
|
||||
kickstartTemplate?: unknown;
|
||||
model?: unknown;
|
||||
reasoning?: unknown;
|
||||
lifecycle?: { enabled?: unknown; desired_state?: unknown };
|
||||
launch?: { yolo?: unknown };
|
||||
}>;
|
||||
connector?: {
|
||||
kind?: unknown;
|
||||
@@ -216,7 +221,17 @@ function normalizeFleetRosterV1Unchecked(raw: RawFleetRoster): FleetRoster {
|
||||
'runtimes',
|
||||
'agents',
|
||||
'connector',
|
||||
// stack#1380 verification unblock: the fleet's own roster-v2 mutation
|
||||
// tooling writes a `generation` fence on the same v1 body. Tolerated here
|
||||
// as an opaque non-negative integer; comms semantics are unchanged.
|
||||
'generation',
|
||||
]);
|
||||
if (
|
||||
raw.generation !== undefined &&
|
||||
(typeof raw.generation !== 'number' || !Number.isInteger(raw.generation) || raw.generation < 0)
|
||||
) {
|
||||
throw new Error('Fleet roster generation must be a non-negative integer.');
|
||||
}
|
||||
if (raw.tmux !== undefined) {
|
||||
assertObject(raw.tmux, 'Fleet roster tmux');
|
||||
assertKnownKeys(raw.tmux, 'Fleet roster tmux', [
|
||||
@@ -231,6 +246,8 @@ function normalizeFleetRosterV1Unchecked(raw: RawFleetRoster): FleetRoster {
|
||||
assertKnownKeys(raw.defaults, 'Fleet roster defaults', [
|
||||
'working_directory',
|
||||
'workingDirectory',
|
||||
// stack#1380 verification unblock: roster-v2 default runtime hint.
|
||||
'runtime',
|
||||
]);
|
||||
}
|
||||
if (raw.runtimes !== undefined) {
|
||||
@@ -243,7 +260,9 @@ function normalizeFleetRosterV1Unchecked(raw: RawFleetRoster): FleetRoster {
|
||||
]);
|
||||
}
|
||||
}
|
||||
if (raw.version !== 1) throw new Error('Fleet roster version must be 1.');
|
||||
if (raw.version !== 1 && raw.version !== 2) {
|
||||
throw new Error('Fleet roster version must be 1 or 2.');
|
||||
}
|
||||
if (raw.transport !== 'tmux') throw new Error('Fleet roster transport must be "tmux".');
|
||||
if (!Array.isArray(raw.agents) || raw.agents.length === 0) {
|
||||
throw new Error('Fleet roster must define at least one agent.');
|
||||
@@ -318,7 +337,52 @@ function normalizeAgent(raw: NonNullable<RawFleetRoster['agents']>[number]): Fle
|
||||
'resetBetweenTasks',
|
||||
'kickstart_template',
|
||||
'kickstartTemplate',
|
||||
// stack#1380 verification unblock: roster-v2 envelope fields written by
|
||||
// the fleet's own mutation tooling. Validated, then opaque to comms.
|
||||
'model',
|
||||
'reasoning',
|
||||
'lifecycle',
|
||||
'launch',
|
||||
]);
|
||||
if (raw.model !== undefined && typeof raw.model !== 'string') {
|
||||
throw new Error('Fleet roster agent model must be a string.');
|
||||
}
|
||||
if (raw.reasoning !== undefined && typeof raw.reasoning !== 'string') {
|
||||
throw new Error('Fleet roster agent reasoning must be a string.');
|
||||
}
|
||||
const lifecycle = raw.lifecycle as { enabled?: unknown; desired_state?: unknown } | undefined;
|
||||
if (lifecycle !== undefined) {
|
||||
if (typeof lifecycle !== 'object' || lifecycle === null) {
|
||||
throw new Error('Fleet roster agent lifecycle must be an object.');
|
||||
}
|
||||
const lifecycleKeys = Object.keys(lifecycle);
|
||||
if (!lifecycleKeys.every((key) => key === 'enabled' || key === 'desired_state')) {
|
||||
throw new Error('Fleet roster agent lifecycle has unknown field(s).');
|
||||
}
|
||||
if (lifecycle.enabled !== undefined && typeof lifecycle.enabled !== 'boolean') {
|
||||
throw new Error('Fleet roster agent lifecycle.enabled must be a boolean.');
|
||||
}
|
||||
if (
|
||||
lifecycle.desired_state !== undefined &&
|
||||
(typeof lifecycle.desired_state !== 'string' ||
|
||||
!['running', 'stopped'].includes(lifecycle.desired_state))
|
||||
) {
|
||||
throw new Error('Fleet roster agent lifecycle.desired_state must be running|stopped.');
|
||||
}
|
||||
}
|
||||
const launch = raw.launch as { yolo?: unknown } | undefined;
|
||||
if (launch !== undefined) {
|
||||
if (typeof launch !== 'object' || launch === null) {
|
||||
throw new Error('Fleet roster agent launch must be an object.');
|
||||
}
|
||||
const launchKeys = Object.keys(launch);
|
||||
if (!launchKeys.every((key) => key === 'yolo')) {
|
||||
throw new Error('Fleet roster agent launch has unknown field(s).');
|
||||
}
|
||||
if (launch.yolo !== undefined && typeof launch.yolo !== 'boolean') {
|
||||
throw new Error('Fleet roster agent launch.yolo must be a boolean.');
|
||||
}
|
||||
}
|
||||
const name = stringValue(raw.name, '', 'Fleet roster agent name');
|
||||
const runtime = stringValue(
|
||||
raw.runtime,
|
||||
|
||||
Reference in New Issue
Block a user