Make agent creation swarm-only and refuse a name placed on another hive
swarm-controller's POST /api/agents now refuses (409) a name the swarm
has already placed on a different hive: a non-Destroyed declaration in
that hive's wanted state, or a SetAgentWanted node still queued for it.
The same name on the same hive is that agent being re-created and goes
through. A wanted state that cannot be read refuses (503/500) instead of
reading as "placed nowhere". Creations are serialised from that read to
the graph insert so two concurrent creations of one name cannot both
pass.
Hive-level creation is removed: hivectl `agent create` / `request-create`,
HostRequest::Spawn / RequestSpawn, the dashboard POST /api/request-spawn
route, and ApprovalKind::Spawn with its approve/resolve arms and the
approval-carrying `templates::spawn`. The swarm path (deploy request or
wanted-state sweep -> queue_first_deploy -> templates::first_deploy) used
none of them. Old `spawn` approval rows are skipped by collect_lenient,
as `init_config` rows were in a3b672d1.
policy.rs's comment on agent_object_name stated swarm-wide name
uniqueness as a fact; it now says where it is enforced and what that
check cannot see.
Refs #4396
This commit is contained in:
parent
1d8ec00ddc
commit
5785c0024c
35 changed files with 376 additions and 434 deletions
|
|
@ -114,7 +114,7 @@ export function operatorInboxAppendFromEvent(ev) {
|
|||
onCountsChanged();
|
||||
}
|
||||
|
||||
// ─── approvals — the operator config-change / spawn approval queue ────────
|
||||
// ─── approvals — the operator config-change approval queue ────────
|
||||
const APPROVAL_TAB_KEY = "hyperhive:approvals:tab";
|
||||
// Derived approval state — cold-loaded from /api/state, then mutated
|
||||
// live by `approval_added` / `approval_resolved` dashboard events.
|
||||
|
|
@ -244,24 +244,14 @@ export function renderApprovals() {
|
|||
el(
|
||||
"span",
|
||||
{ class: "glyph" },
|
||||
isMergePr ? "⇒" : isUpdateMeta ? "↻" : isSchedule ? "⏱" : "⊕",
|
||||
isUpdateMeta ? "↻" : isSchedule ? "⏱" : "⇒",
|
||||
),
|
||||
el("span", { class: "id" }, "#" + a.id),
|
||||
el("span", { class: "agent" }, a.agent),
|
||||
el(
|
||||
"span",
|
||||
{
|
||||
class:
|
||||
"kind" +
|
||||
(isMergePr || isUpdateMeta || isSchedule ? "" : " kind-spawn"),
|
||||
},
|
||||
isMergePr
|
||||
? "merge-pr"
|
||||
: isUpdateMeta
|
||||
? "meta-update"
|
||||
: isSchedule
|
||||
? "schedule"
|
||||
: "spawn",
|
||||
{ class: "kind" },
|
||||
isUpdateMeta ? "meta-update" : isSchedule ? "schedule" : "merge-pr",
|
||||
),
|
||||
);
|
||||
if (isMergePr && a.sha_short) head.append(el("code", {}, a.sha_short));
|
||||
|
|
@ -431,13 +421,11 @@ function renderApprovalHistory(root, history) {
|
|||
el(
|
||||
"span",
|
||||
{ class: "kind" },
|
||||
a.kind === "merge_config_pr"
|
||||
? "merge-pr"
|
||||
: a.kind === "update_meta_inputs"
|
||||
? "meta-update"
|
||||
: a.kind === "schedule_prompt"
|
||||
? "schedule"
|
||||
: "spawn",
|
||||
a.kind === "update_meta_inputs"
|
||||
? "meta-update"
|
||||
: a.kind === "schedule_prompt"
|
||||
? "schedule"
|
||||
: "merge-pr",
|
||||
),
|
||||
" ",
|
||||
);
|
||||
|
|
|
|||
|
|
@ -671,10 +671,6 @@ ul form.inline {
|
|||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.kind-spawn {
|
||||
color: var(--amber);
|
||||
border-color: var(--amber);
|
||||
}
|
||||
details {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,10 +81,9 @@ window.marked = marked;
|
|||
for (const a of approvals) {
|
||||
if (seenApprovals.has(a.id)) continue;
|
||||
seenApprovals.add(a.id);
|
||||
const verb = a.kind === "spawn" ? "spawn approval" : "config commit";
|
||||
NOTIF.show(
|
||||
"◆ approval #" + a.id,
|
||||
`${verb} for ${a.agent}`,
|
||||
`config commit for ${a.agent}`,
|
||||
"hyperhive:approval:" + a.id,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue