Commit Graph

2 Commits

Author SHA1 Message Date
Anso 2821e87d3f fix: gate fleet stop-by-label on a resolved preview and validate remote stop responses (#1392)
* fix: gate fleet stop-by-label on a resolved preview and validate remote stop responses

The destructive "Stop fleet by label" action could run before its blast
radius was known, and a malformed remote response could be rendered as a
successful zero-stack stop. Both are release blockers for the fleet
stop-by-label flow.

Gate the "Stop fleet" button on a resolved blast radius: the live
match-preview resolving to at least one matching stack, or a dry run of the
current label when the preview endpoint is unavailable. Carry the resolved
node and stack list into the confirm modal so the operator confirms against
the concrete targets rather than a label name alone. Editing the label
invalidates a prior dry-run snapshot, so a stale blast radius cannot
re-enable the action.

Validate the remote local-stop 200 body before trusting it. A body that is
not the local-stop contract (missing matched flag, non-array results, or a
malformed result element) now fails that node with a clear error, consistent
with the non-ok and unreachable paths, instead of defaulting matched to true
and results to empty.

* fix: ignore stale fleet stop-by-label preview responses after the label changes

The debounced match-preview callback set the preview state unconditionally,
so a request issued for one label that resolved after the operator switched
to another label would mark the preview ready with the old label's blast
radius. That re-enabled the destructive Stop and showed stale nodes/stacks
in the confirm modal for a label that no longer matched them.

Guard the in-flight request with a per-run cancelled flag flipped in the
effect cleanup, so a response for a label that is no longer current cannot
set the preview. This mirrors the cancellation pattern already used by the
suggestions effect. Add a test that holds a preview request in-flight,
switches the label, then resolves the stale response and asserts Stop stays
disabled and the old targets do not leak.
2026-06-18 23:29:20 -04:00
Anso 7e0cffa376 fix(fleet-actions): stop-by-label works on Community remote nodes (#1270)
* fix(fleet-actions): stop-by-label works on Community remote nodes

Fleet-stop's remote leg fanned out to POST /api/labels/:id/action, which
is gated to Skipper/Admiral, so on a Community fleet the control node
stopped its own stacks but every remote node returned 403. Fleet-stop
itself is admin-only and available on every license, so the remote leg
contradicted the feature's own gate.

Extract the label-match plus bulk-stop logic into a shared
runLocalLabelStop helper and add an admin-only, every-license
POST /api/fleet-actions/labels/local-stop receiver. The control now fans
out to that receiver, so remote stacks stop on every tier. Each node runs
under its own per-node bulk lock, so a fleet-stop and a per-label action
still serialize cleanly instead of double-stopping containers.

Also degrade the control's own leg per-node instead of failing the whole
fan-out when its filesystem read throws, and gate fleet-stop and
fleet-prune diagnostics behind developer_mode.

Tests: local-stop auth, tier, validation, and behavior; a remote-leg
routing guard that asserts the fan-out targets local-stop and never the
paid route; local-leg graceful degradation; and the three Fleet Action
card UIs.

* fix(fleet-actions): honor the remote stop receiver's matched flag

The control reached the remote leg only because its own mirror had the
label, then hardcoded matched:true and trusted results without guarding
its shape. A mirror-skewed control (mirror has the label, remote does
not) then showed a remote mismatch as "matched, 0 stacks" instead of
"no matching label", and a malformed 200 body could flow a non-array
into the per-stack renderers.

Honor the remote's own matched flag and coerce results to an array when
the body is malformed. Add regression tests for the matched:false skew
case and the non-array results case.
2026-06-01 14:18:44 -04:00