fix(fleet): refuse v2 add/remove cleanly, and pin the Condition's effect
Two follow-ups from the canary red->green run and scooby's review.
1. The v2 refusal in `add`/`remove` was a bare `throw`, which reaches the CLI
top level uncaught and prints the guidance under a Node stack trace. The
message *is* the point of the refusal, so it now goes through
`command.error()` — the same clean path the roster-config error uses.
Caught on canary, not in review: the unit tests asserted the message text
and passed either way.
2. The unit-template test asserted only that ConditionPathExists is present.
Presence is not effect. Added two tests for the parts that can drift in
code while that assertion still passes: the condition resolving to exactly
the file the fleet writes (%h/%i rendered against a real install), and the
launcher genuinely failing on an absent generated env (exit 64,
`missing-file`) — which is what makes the condition load-bearing rather
than decorative.
systemd is not available in the suite, so the effect itself was measured on
canary (2026-08-16), roster v2 generation 3:
with the condition: start rc=0, Result=success, ConditionResult=no,
journal "skipped, unmet condition check"
condition removed by
drop-in, nothing else: start rc=1, Result=exit-code, ExecMainStatus=64,
unit failed, "agent environment rejected: missing-file"
Canary red->green for the three commands, same v2 roster, side by side:
fleet ps 0.0.50-next.2413 rc=1 -> branch rc=0 (3 agents listed)
fleet install 0.0.50-next.2413 rc=1 -> branch rc=0
fleet remove <name> 0.0.50-next.2413 rc=1 -> branch rc=1, refusal naming
delete + apply
All three previously failed with "Fleet roster has unknown field(s):
generation." The #791 negative was measured too: the six existing
*.env.generated files were untouched by `install` (mtimes 20+ minutes older
than the run).
Gates: typecheck 0, eslint 0, prettier clean, fleet specs 382 passed, new spec
10/10 with the fix and 9/10 red against origin/next (the 10th passes there for
an unrelated reason and is annotated as such). Full suite: only
mutator-gate.acceptance.spec.ts fails, pre-existing on origin/next.
Still true and still worth saying: a correct fix here shows install rc=0 and
start rc=0 and STILL no live seat. #1240 (tmux absent) is upstream, #1241
(start reports lifecycle-complete over dead panes) and the missing agent
runtime are downstream.
Refs #1237
Reviewed-by: scooby (by git comms; cannot file a Gitea review from fomo-lin)
This commit is contained in:
@@ -1914,7 +1914,14 @@ export function registerFleetCommand(program: Command, deps: FleetCommandDeps =
|
||||
},
|
||||
) => {
|
||||
if (await usesRosterV2ControlPlane(cmd)) {
|
||||
throw new Error(rosterV2MutationGuidance('add', 'create', name));
|
||||
// command.error, not a bare throw: this is operator guidance, and a
|
||||
// bare throw reaches the top level uncaught and prints it under a Node
|
||||
// stack trace. Measured on canary — the message is the whole point of
|
||||
// the refusal, so it has to arrive readable.
|
||||
cmd.error(rosterV2MutationGuidance('add', 'create', name), {
|
||||
code: 'fleet.roster-v2',
|
||||
exitCode: 1,
|
||||
});
|
||||
}
|
||||
if (!VALID_FLEET_RUNTIMES.includes(opts.runtime)) {
|
||||
throw new Error(
|
||||
@@ -1982,7 +1989,10 @@ export function registerFleetCommand(program: Command, deps: FleetCommandDeps =
|
||||
.option('--keep-files', 'Skip deleting env and heartbeat files')
|
||||
.action(async (name: string, opts: { keepFiles?: boolean }) => {
|
||||
if (await usesRosterV2ControlPlane(cmd)) {
|
||||
throw new Error(rosterV2MutationGuidance('remove', 'delete', name));
|
||||
cmd.error(rosterV2MutationGuidance('remove', 'delete', name), {
|
||||
code: 'fleet.roster-v2',
|
||||
exitCode: 1,
|
||||
});
|
||||
}
|
||||
const commandOpts = cmd.opts<{ mosaicHome: string; roster?: string }>();
|
||||
const activePaths = resolveFleetPaths(commandOpts.mosaicHome);
|
||||
|
||||
Reference in New Issue
Block a user