hive-c0re: fail on a malformed agent name and on an unreadable container list
An agent name that is not a valid Ident made `Coordinator::agent_paths` panic. Job payloads carry names as plain strings (the swarm's published wanted state is one source), and a panic inside a job-queue node never reaches `complete_growing`, so the node's resources (the deploy window included) were held until hive-c0re restarted. `agent_paths` now returns an error; the job-queue nodes, the admin-socket spawn and set-limits paths, the root-agent spawn and the dashboard set-limits handler propagate it. `lifecycle::list().await.unwrap_or_default()` turned a failed container list into "no agents": - meta-update cascade: the lock bump committed and zero rebuilds fanned out, reported as success. The cascade is now resolved before the lock bump and a list failure fails the node. - dashboard update-all: queued nothing and returned 200 "ok". Now 500 with the error. - container rescan: every row was emitted as removed and the cache emptied. Now the last snapshot stands; `hivectl status` gets an error. - dashboard journal: answered 404 "no managed container". Now 500. - spawn/rebuild port-collision check: silently skipped. Now fails. - startup migration: the per-agent phases ran over nothing, and phase 3 handed an empty agent list to `meta::sync_agents`, which renders the meta flake with exactly the agents it is given. Both now log the list failure and skip. The hive-jobq scheduler still leaks a node's resources on any executor panic; that root is not addressed here. Refs #4723
This commit is contained in:
parent
ee25b7de20
commit
7ac6819652
10 changed files with 153 additions and 67 deletions
|
|
@ -399,7 +399,7 @@ async fn run_meta_sync(coord: &Arc<Coordinator>, name: &str, relock: bool) -> Re
|
|||
// listener (event-driven: registered at start/create).
|
||||
let agent_dir = crate::paths::agent_runtime_dir(name);
|
||||
let hive = coord.hive_env();
|
||||
let paths = Coordinator::agent_paths(name, agent_dir);
|
||||
let paths = Coordinator::agent_paths(name, agent_dir)?;
|
||||
crate::lifecycle::prepare_rebuild_dirs(name, &paths).await?;
|
||||
// Idempotent meta sync so a manual rebuild can also recover from a
|
||||
// divergent meta repo; then bump just this agent's input. `relock =
|
||||
|
|
@ -442,7 +442,7 @@ async fn run_swap(coord: &Arc<Coordinator>, name: &str, id: NodeId) -> Result<()
|
|||
// and listener were created earlier. Pure path accessor suffices.
|
||||
let agent_dir = crate::paths::agent_runtime_dir(name);
|
||||
let hive = coord.hive_env();
|
||||
let paths = Coordinator::agent_paths(name, agent_dir);
|
||||
let paths = Coordinator::agent_paths(name, agent_dir)?;
|
||||
let result = crate::lifecycle::swap_update(name, &hive, &paths, Some(id.get())).await;
|
||||
// On success the Ok-only bookkeeping tail (rev marker, forge/matrix
|
||||
// sync, kick, rescan, snapshot) runs in the sibling `RebuildBookkeeping` node,
|
||||
|
|
@ -492,7 +492,7 @@ async fn run_rebuild_bookkeeping(coord: &Arc<Coordinator>, name: &str) -> Result
|
|||
async fn run_provision(coord: &Arc<Coordinator>, name: &str) -> Result<()> {
|
||||
let agent_dir = crate::paths::agent_runtime_dir(name);
|
||||
let hive = coord.hive_env();
|
||||
let paths = Coordinator::agent_paths(name, agent_dir);
|
||||
let paths = Coordinator::agent_paths(name, agent_dir)?;
|
||||
crate::lifecycle::provision_container(name, &hive, &paths).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -536,13 +536,15 @@ async fn run_meta_lock(
|
|||
return Ok(fanout.unwrap_or_default());
|
||||
}
|
||||
let _progress = coord.meta_update_guard();
|
||||
// Resolved before the lock bump so a failed container list aborts
|
||||
// the update before anything is committed.
|
||||
let cascade = match fanout {
|
||||
Some(list) => validate_agent_names(list),
|
||||
None => meta_update_cascade_agents(inputs).await?,
|
||||
};
|
||||
crate::meta::lock_update(inputs).await?;
|
||||
// Lock file changed — meta-inputs panel re-renders.
|
||||
crate::dashboard::emit_meta_inputs_snapshot(coord);
|
||||
let cascade = match fanout {
|
||||
Some(list) => validate_agent_names(list),
|
||||
None => meta_update_cascade_agents(inputs).await,
|
||||
};
|
||||
// Pull each cascade agent's own input too — an agent's config-repo main
|
||||
// is trusted, so a meta-input bump is a reasonable place to also catch
|
||||
// it up. Without this, a meta-input bump rebuilds every affected agent
|
||||
|
|
@ -627,7 +629,7 @@ async fn run_start(coord: &Arc<Coordinator>, name: &str) -> Result<()> {
|
|||
// omitting this becomes a compile error.
|
||||
let agent_dir = crate::paths::agent_runtime_dir(name);
|
||||
let hive = coord.hive_env();
|
||||
let paths = Coordinator::agent_paths(name, agent_dir);
|
||||
let paths = Coordinator::agent_paths(name, agent_dir)?;
|
||||
let token = crate::lifecycle::converge_start_preamble(name, &hive, &paths).await?;
|
||||
crate::lifecycle::start_with_fallback(token).await?;
|
||||
// Bind the MCP listener immediately after starting the container.
|
||||
|
|
@ -764,7 +766,7 @@ async fn run_write_dropin(coord: &Arc<Coordinator>, name: &str) -> Result<()> {
|
|||
// on the upstream Prebuild/Start node).
|
||||
let agent_dir = crate::paths::agent_runtime_dir(name);
|
||||
let hive = coord.hive_env();
|
||||
let paths = Coordinator::agent_paths(name, agent_dir);
|
||||
let paths = Coordinator::agent_paths(name, agent_dir)?;
|
||||
crate::lifecycle::write_dropins(name, &hive, &paths).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -870,26 +872,23 @@ async fn run_deploy_tail(
|
|||
/// cascade agent's name. The `touched_hyperhive` branch's names need no
|
||||
/// such filter: they come from `lifecycle::list()`, already real
|
||||
/// container names by construction, not parsed out of caller input.
|
||||
pub async fn meta_update_cascade_agents(inputs: &[String]) -> Vec<String> {
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The every-container branch could not list the containers.
|
||||
pub async fn meta_update_cascade_agents(inputs: &[String]) -> Result<Vec<String>> {
|
||||
let touched_hyperhive = inputs
|
||||
.iter()
|
||||
.any(|i| i == "hyperhive" || i.starts_with("hyperhive/"));
|
||||
let touched_agents = parse_agent_input_names(inputs);
|
||||
let mut names = if touched_hyperhive || inputs.is_empty() {
|
||||
crate::lifecycle::list()
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.filter_map(|c| {
|
||||
c.strip_prefix(crate::lifecycle::AGENT_PREFIX)
|
||||
.map(str::to_owned)
|
||||
})
|
||||
.collect()
|
||||
crate::lifecycle::agent_names(crate::lifecycle::list().await)
|
||||
.context("listing containers for the meta-update cascade")?
|
||||
} else {
|
||||
touched_agents
|
||||
};
|
||||
names.sort();
|
||||
names
|
||||
Ok(names)
|
||||
}
|
||||
|
||||
/// Parse `agent-<name>` input strings into validated agent names. Pure and
|
||||
|
|
|
|||
Loading…
Reference in a new issue