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
|
|
@ -558,21 +558,22 @@ impl Coordinator {
|
|||
/// from `crate::paths::agent_runtime_dir(name)` (pure path) or from
|
||||
/// `lifecycle::ensure_agent_runtime_dir(name)` when the dir must be
|
||||
/// created. All other paths are derived statically from `name`.
|
||||
#[must_use]
|
||||
pub fn agent_paths(name: &str, agent_dir: PathBuf) -> AgentPaths {
|
||||
// `name` is validated upstream (spawn-approval / enqueue gate / the
|
||||
// MANAGER_NAME const), so an invalid ident here is a construction
|
||||
// bug. This is the step-3 boundary between the Ident-threaded path
|
||||
// builders and the job_queue layer (threaded post hive-jobq cutover).
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// `name` is not a valid [`hive_types::Ident`]. Job payloads carry agent
|
||||
/// names as plain strings, one source being the swarm's published wanted
|
||||
/// state, so this is an error for the caller to report, not an invariant.
|
||||
pub fn agent_paths(name: &str, agent_dir: PathBuf) -> Result<AgentPaths> {
|
||||
let name = hive_types::Ident::parse(name)
|
||||
.expect("agent_paths: name must be a valid ident (validated at spawn/enqueue)");
|
||||
AgentPaths {
|
||||
.map_err(|e| anyhow::anyhow!("invalid agent name {name:?}: {e}"))?;
|
||||
Ok(AgentPaths {
|
||||
agent: agent_dir,
|
||||
proposed: Self::agent_proposed_dir(&name),
|
||||
applied: crate::paths::applied_dir(name.as_str()),
|
||||
claude: Self::agent_claude_dir(&name),
|
||||
notes: Self::agent_notes_dir(&name),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Emit a `RebuildQueueChanged` tick. Called from the queue mutation
|
||||
|
|
@ -845,7 +846,15 @@ impl Coordinator {
|
|||
/// Cheap when nothing changed (one `nixos-container list` + a
|
||||
/// `HashMap` diff + zero emits).
|
||||
pub async fn rescan_containers_and_emit(self: &Arc<Self>) {
|
||||
let fresh = container_view::build_all(&self.hive_env()).await;
|
||||
// An unreadable list says nothing about which containers exist, so
|
||||
// the last snapshot stands rather than every row being reported gone.
|
||||
let fresh = match container_view::build_all(&self.hive_env()).await {
|
||||
Ok(fresh) => fresh,
|
||||
Err(e) => {
|
||||
tracing::warn!(error = ?e, "container rescan: list failed; keeping last snapshot");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mut last = self.last_containers.lock().await;
|
||||
let mut changed_or_new = Vec::new();
|
||||
let mut removed = Vec::new();
|
||||
|
|
@ -1580,3 +1589,24 @@ mod rebuilt_todo_summary_tests {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod agent_paths_tests {
|
||||
use super::Coordinator;
|
||||
|
||||
/// A name from a job payload that is not an `Ident` fails the caller's
|
||||
/// node instead of panicking the executor task.
|
||||
#[test]
|
||||
fn a_malformed_name_is_an_error_not_a_panic() {
|
||||
let err = Coordinator::agent_paths("../etc", std::path::PathBuf::from("/nonexistent"))
|
||||
.expect_err("a malformed agent name must be rejected");
|
||||
assert!(format!("{err:#}").contains("invalid agent name"), "{err:#}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_valid_name_builds_its_paths() {
|
||||
let paths = Coordinator::agent_paths("iris", std::path::PathBuf::from("/run/x"))
|
||||
.expect("a valid agent name");
|
||||
assert_eq!(paths.agent, std::path::PathBuf::from("/run/x"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue