Watch
0
0
Fork
You've already forked hyperhive
0

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:
atlas 2026-09-29 15:11:57 +02:00
commit 5785c0024c
35 changed files with 376 additions and 434 deletions

View file

@ -1624,10 +1624,10 @@ fn pause_shape_signal_drain() {
}
#[test]
fn spawn_shape_provision_create_dropin_reconcile() {
fn first_deploy_shape_provision_create_dropin_reconcile() {
let q = JobQueue::new(1);
insert(&q, |builder| {
templates::spawn(builder, "newbie", 7);
templates::first_deploy(builder, "newbie");
});
assert_eq!(
declared_shape(&q),
@ -1636,14 +1636,6 @@ fn spawn_shape_provision_create_dropin_reconcile() {
row("create", Some("provision"), &[]),
row("write_dropin", Some("create"), &[]),
row("reconcile", Some("create"), &[("write_dropin", "done")]),
// One tail per outcome, each edged to accept only that one — so
// *which* tail the graph lets run already is the answer, and
// nothing branches at runtime. The three differ **only** in their
// accepted outcome, which is why `declared_shape` spells the
// outcome set out instead of bucketing it.
row("resolve_approval", None, &[("provision", "done")]),
row("resolve_approval", None, &[("provision", "failed")]),
row("resolve_approval", None, &[("provision", "cancelled")]),
]
);
}