Watch
0
0
Fork
You've already forked hyperhive
0

config PRs: an operator's Forgejo merge deploys the merged rev

A config PR merged in the Forgejo UI changed nothing on the hive: the
hive's webhook ignores `closed`, its poll then cancels the dashboard
card, and `applied/main` stays where it was.

swarm-controller reads `merged`/`merge_commit_sha` off the
`pull_request` delivery it already receives for `agent-configs`, finds
the hive placing the agent by scanning every hive's wanted state (the
scan `declarations_elsewhere` already ran, factored out), and queues a
`TriggerDeploy` carrying the rev. Zero or several claimants deploy
nothing and log the claimants.

`DeployRequest` gains `rev: Option<String>` with `serde(default)`, so
rev-less payloads from either side keep decoding.

hive-c0re, given a rev for an agent it runs: a no-op when
`applied/main` already is the rev (a dashboard merge deploys its own
PR); otherwise it fetches the forge `main` with the core token,
requires the rev to descend from `applied/main` (the ancestry gate,
factored out of `run_deploy_merge_verify`), fast-forwards by CAS and
queues the usual relocking rebuild. No eval-verify on this path, per
mara (#4850 c90075). A refusal is commented on the PR that merged the
rev, found by commit.

swarm-controller's forge-objects pass converges every config repo's
`main` rule to merge whitelist `operators` + `core` and approval
whitelist `operators`. The hive's boot PATCH stops forcing
`enable_approvals_whitelist` off, so the two do not fight.

Refs #4850
This commit is contained in:
atlas 2026-10-02 18:45:18 +02:00
commit f1c695c212
11 changed files with 732 additions and 76 deletions

View file

@ -144,14 +144,14 @@ pub fn spawn(
/// Listen on the swarm's event subjects and act on what arrives.
///
/// The controller decides *what a forge delivery means* and addresses the
/// result here; this end does not know a forge exists. Two events today:
/// result here; this end reads no forge delivery. Two events today:
/// **the knowledge repository changed**, answered by the pull this daemon
/// already runs at boot, and **deploy this agent**, answered by the same
/// rebuild insert the operator's own verb makes.
///
/// Only the deploy event carries a payload, and only the agent name: its
/// subject already names the hive, so it listens on its own rather than a
/// swarm-wide feed. Even then it is a trigger, never the config git owns.
/// Only the deploy event carries a payload: the agent name, and the config
/// commit to deploy when the swarm names one. Its subject already names the
/// hive, so it listens on its own rather than a swarm-wide feed.
///
/// # A missed message costs the two events very differently
///
@ -264,7 +264,7 @@ async fn handle_deploy_request(
return;
}
};
let agent = request.agent;
let swarm_queue_client::DeployRequest { agent, rev } = request;
// Which of the two meanings this request has is decided here, and the
// predicate is "does a container exist", not "is one running":
@ -283,6 +283,15 @@ async fn handle_deploy_request(
}
};
// Only a rebuild applies `rev`. A first deploy ignores it and builds the
// `applied` repo it seeds from the forge's `main`, or the one it finds.
if known
&& let Some(rev) = rev.as_deref()
&& !apply_rev(&agent, rev).await
{
return;
}
let inserted = if known {
// The same insert the operator's own `rebuild` verb makes, relock and
// all: "deploy this agent" means here exactly what it already meant, and
@ -303,6 +312,29 @@ async fn handle_deploy_request(
}
}
/// Move `agent`'s `applied/main` to `rev` ahead of its rebuild, and say
/// whether that rebuild should be queued. A `rev` already applied is taken as
/// deployed or deploying — a dashboard merge deploys the PR it merges — so
/// no second rebuild is queued. That also skips a rev put in place by
/// `hivectl forge reconcile-config`, which does not deploy.
///
/// A refusal is commented on the PR that merged `rev`. A failed rebuild is
/// not: it surfaces where every rebuild failure does, on this hive.
async fn apply_rev(agent: &str, rev: &str) -> bool {
match crate::actions::advance_applied_to_rev(agent, rev).await {
Ok(crate::actions::RevAdvance::Advanced) => true,
Ok(crate::actions::RevAdvance::AlreadyApplied) => {
tracing::info!(%agent, %rev, "swarm events: deploy requested for the applied rev; nothing queued");
false
}
Err(e) => {
tracing::warn!(%agent, %rev, error = %format!("{e:#}"), "swarm events: rev not applied; nothing queued");
crate::actions::post_rev_deploy_failure_to_pr(agent, rev, &e).await;
false
}
}
}
/// Queue the first deploy of an agent this hive does not have yet: seed its
/// power intent, then insert the DAG.
///