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

@ -232,56 +232,3 @@ pub(super) async fn post_op_send(
// no full-state refresh in between.
(axum::http::StatusCode::OK, "ok").into_response()
}
#[derive(Deserialize, ToSchema)]
pub(super) struct RequestSpawnForm {
name: String,
}
/// Queue a spawn approval for `name`.
#[utoipa::path(
post,
path = "/api/request-spawn",
request_body(content = RequestSpawnForm, content_type = "application/x-www-form-urlencoded"),
responses(
(status = 200, description = "spawn approval queued", body = String),
(status = 500, description = "missing name, or the approval submit failed"),
),
tag = "misc_api"
)]
pub(super) async fn post_request_spawn(
State(state): State<AppState>,
Form(form): Form<RequestSpawnForm>,
) -> Response {
let name = form.name.trim().to_owned();
if name.is_empty() {
return error_response("spawn: `name` required");
}
match state.coord.approvals.submit_kind(
&name,
hive_sh4re::approvals::ApprovalKind::Spawn,
"",
None,
"operator",
None,
) {
Ok(id) => {
tracing::info!(%id, %name, "operator: spawn approval queued via dashboard");
// Phase 5b: notify the dashboard event channel so live
// subscribers can append the row without a snapshot
// refetch. Spawn approvals carry no sha.
state
.coord
.emit_approval_added(crate::coordinator::ApprovalAdded {
id,
agent: &name,
approval_kind: "spawn",
sha_short: None,
description: None,
pr_number: None,
});
(StatusCode::OK, "ok").into_response()
}
Err(e) => error_response(&format!("request-spawn {name} failed: {e:#}")),
}
}