Watch
0
0
Fork
You've already forked hyperhive
0

swarm-controller: state route refuses an agent the swarm has not declared on that hive

A placing state is accepted only for an agent declared or queued on the hive; anything else gets 404 telling the caller to create it at swarm level. swarm-ui's wanted badge no longer offers a first declaration for an undeclared row.
This commit is contained in:
atlas 2026-10-25 15:58:24 +01:00
commit 716290e4d2
3 changed files with 131 additions and 40 deletions

View file

@ -1,13 +1,8 @@
// <WantedMenu> — the "wanted" state control: one badge showing the
// current declaration, opening a `Dropdown` with the four explicit
// states — replaces the old toggle-badge-plus-separate-destroy-badge
// pair. Explicit options also fix a real bug the toggle had: for an
// undeclared row, the toggle inferred a target as "the opposite of
// `snapshot.running`", so a click on an undeclared-but-running agent
// silently declared it *offline* rather than making its real state
// explicit — a dropdown just lets "up" be picked directly regardless
// of what's inferred, no flip-to-the-opposite-of-a-guess involved
// (mara: "when no state is declared, i want to set it to online").
// states. An agent with no declaration has none to change: its badge is
// disabled and points at creating it at swarm level, because the state
// route refuses an agent the swarm never declared on that hive.
// Presentational only, same split as `StatusChips`'s
// `StatusMenu`: the caller owns what each selection actually does.
//
@ -66,6 +61,8 @@ export function WantedMenu({
const [open, setOpen] = useState(false);
const anchorRef = useRef<HTMLDivElement>(null);
const destroyed = row.wanted === "destroyed";
const undeclared = !row.wanted;
const canDeclare = !!row.hive && !destroyed && !undeclared;
const tone: BadgeTone =
row.wanted === "up"
? "positive"
@ -87,20 +84,20 @@ export function WantedMenu({
<div class="ui-dropdown-anchor" ref={anchorRef}>
<Badge
tone={tone}
value={pending ? "…" : (row.wanted ?? "no declaration")}
// `destroyed` blocks opening the menu at all — same UX-only
// guard as before (real enforcement is server-side, see
// `declareState`'s caller), just applied to the trigger instead
// of a `disabled` toggle badge.
onClick={row.hive && !destroyed ? () => setOpen((o) => !o) : undefined}
// `destroyed` and an undeclared row block opening the menu at
// all — a UX-only guard, the server enforces both (see
// `declareState`'s caller).
onClick={canDeclare ? () => setOpen((o) => !o) : undefined}
expanded={open}
disabled={pending || !row.hive || destroyed}
disabled={pending || !canDeclare}
title={
destroyed
? `${row.name} is destroyed — redeploy via "+ agent" to bring it back`
: row.hive
? `declare a new state for ${row.name}`
: "no hive on record for this agent — nothing to declare against"
: !row.hive
? "no hive on record for this agent — nothing to declare against"
: undeclared
? `${row.name} is not declared at swarm level — create it there`
: `declare a new state for ${row.name}`
}
/>
<Dropdown