From fb726197ea5b199fd2c1e8ea4e9f3417627cfc6a Mon Sep 17 00:00:00 2001 From: damocles Date: Fri, 5 Jun 2026 15:59:45 +0200 Subject: [PATCH] fix(#1375): clean up pedantic warnings and re-enable -D warnings without pedantic bypass --- flake.nix | 2 +- hive-bash-mcp/src/bin/mcp.rs | 8 +++--- hive-bash-mcp/src/paths.rs | 6 ++--- hive-bash-mcp/src/runner.rs | 14 +++++----- hive-c0re/src/agent_server.rs | 6 ++--- hive-c0re/src/auto_update.rs | 2 +- hive-c0re/src/bash_tasks_vacuum.rs | 4 +-- hive-c0re/src/bin/hivectl.rs | 10 +++---- hive-c0re/src/container_view.rs | 4 +-- hive-c0re/src/coordinator.rs | 2 +- hive-c0re/src/dashboard.rs | 3 +-- hive-c0re/src/dashboard_events.rs | 2 +- hive-c0re/src/gateway_nginx.rs | 4 +-- hive-c0re/src/knowledge.rs | 6 ++--- hive-c0re/src/lifecycle.rs | 5 ++-- hive-c0re/src/main.rs | 1 + hive-c0re/src/manager_server.rs | 6 ++--- hive-c0re/src/matrix.rs | 41 ++++++++++++++--------------- hive-c0re/src/meta.rs | 8 ++---- hive-c0re/src/migrate.rs | 2 +- hive-c0re/src/priv_client.rs | 2 +- hive-c0re/src/rebuild_queue.rs | 6 ++--- hive-c0re/src/reminder_scheduler.rs | 2 +- hive-c0re/src/topology.rs | 2 +- hive-matrix-mcp/src/client.rs | 6 +++++ hive-matrix-mcp/src/wake.rs | 6 +++++ hive-priv/src/main.rs | 38 ++++++++++++++++---------- hive-sh4re/src/priv_proto.rs | 1 + 28 files changed, 109 insertions(+), 90 deletions(-) diff --git a/flake.nix b/flake.nix index bafe4a9d..3bd6bff9 100644 --- a/flake.nix +++ b/flake.nix @@ -342,7 +342,7 @@ inherit cargoArtifacts nativeBuildInputs; pname = "hyperhive-workspace"; version = "0.1.0"; - cargoClippyExtraArgs = "--workspace --all-targets -- -D warnings -A clippy::pedantic"; + cargoClippyExtraArgs = "--workspace --all-targets -- -D warnings"; }; # `cargo test --workspace` lifted out of `buildPackage` so the # `hyperhive-assets` dep (which `hive-ag3nt::prompt::tests` diff --git a/hive-bash-mcp/src/bin/mcp.rs b/hive-bash-mcp/src/bin/mcp.rs index 6c87ac62..308f9afc 100644 --- a/hive-bash-mcp/src/bin/mcp.rs +++ b/hive-bash-mcp/src/bin/mcp.rs @@ -65,7 +65,8 @@ fn format_task(id: &str, task: &serde_json::Value) -> String { let now = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap_or_default() - .as_secs() as i64; + .as_secs() + .cast_signed(); let _ = write!(out, ", running for {}s", now - started); } if let (Some(completed), Some(started)) = @@ -76,8 +77,8 @@ fn format_task(id: &str, task: &serde_json::Value) -> String { let out_file = paths::task_out(id); let err_file = paths::task_err(id); - let out_len = std::fs::metadata(&out_file).map(|m| m.len()).unwrap_or(0); - let err_len = std::fs::metadata(&err_file).map(|m| m.len()).unwrap_or(0); + let out_len = std::fs::metadata(&out_file).map_or(0, |m| m.len()); + let err_len = std::fs::metadata(&err_file).map_or(0, |m| m.len()); if let Some(stdout) = task["stdout_tail"].as_str() { let s = stdout.trim(); @@ -151,6 +152,7 @@ struct BashRunArgs { wait_seconds: Option, } +#[allow(clippy::unnecessary_wraps)] fn default_wait() -> Option { Some(3) } diff --git a/hive-bash-mcp/src/paths.rs b/hive-bash-mcp/src/paths.rs index dd4c5a70..87ff125e 100644 --- a/hive-bash-mcp/src/paths.rs +++ b/hive-bash-mcp/src/paths.rs @@ -33,8 +33,7 @@ pub fn tasks_dir() -> PathBuf { let state_path = PathBuf::from(&state); state_path .parent() - .map(|p| p.join("harness")) - .unwrap_or_else(|| PathBuf::from(state)) + .map_or_else(|| PathBuf::from(state), |p| p.join("harness")) }; base.join("bash-tasks") } @@ -64,8 +63,7 @@ pub fn mcp_loose_ends_dir() -> PathBuf { let state_path = PathBuf::from(&state); state_path .parent() - .map(|p| p.join("harness")) - .unwrap_or_else(|| PathBuf::from(state)) + .map_or_else(|| PathBuf::from(state), |p| p.join("harness")) }; base.join("mcp-loose-ends") } diff --git a/hive-bash-mcp/src/runner.rs b/hive-bash-mcp/src/runner.rs index 44358640..48c4c5bf 100644 --- a/hive-bash-mcp/src/runner.rs +++ b/hive-bash-mcp/src/runner.rs @@ -49,7 +49,8 @@ fn now_unix() -> i64 { SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_default() - .as_secs() as i64 + .as_secs() + .cast_signed() } /// Generate a task ID: ``. @@ -112,7 +113,7 @@ fn refresh_loose_ends() { let dest = dir.join("bash.json"); let tmp = dest.with_extension("json.tmp"); let json = serde_json::to_string(&items).unwrap_or_else(|_| "[]".to_owned()); - if let Err(e) = std::fs::write(&tmp, &json).and_then(|_| std::fs::rename(&tmp, &dest)) { + if let Err(e) = std::fs::write(&tmp, &json).and_then(|()| std::fs::rename(&tmp, &dest)) { tracing::warn!(error = ?e, "bash_runner: write mcp-loose-ends/bash.json failed"); } } @@ -220,7 +221,7 @@ async fn run_loop(socket: PathBuf) { let claimed: Arc>> = Arc::new(Mutex::new(HashSet::new())); loop { - poll_once(&socket, &claimed).await; + poll_once(&socket, &claimed); tokio::time::sleep(POLL_INTERVAL).await; } } @@ -255,7 +256,7 @@ async fn mark_interrupted(socket: &Path) { } } -async fn poll_once(socket: &Path, claimed: &Arc>>) { +fn poll_once(socket: &Path, claimed: &Arc>>) { let Ok(rd) = std::fs::read_dir(paths::tasks_dir()) else { return; }; @@ -452,6 +453,8 @@ pub(crate) async fn send_wake( summary: &str, output: Option<(&str, &str)>, ) { + use tokio::io::{AsyncBufReadExt as _, BufReader}; + use tokio::net::UnixStream; let mut body = format!("bash task `{id}` finished: {summary}"); if let Some((stdout, stderr)) = output { if !stdout.is_empty() { @@ -471,9 +474,6 @@ pub(crate) async fn send_wake( transient: true, }; - use tokio::io::{AsyncBufReadExt as _, BufReader}; - use tokio::net::UnixStream; - match UnixStream::connect(socket).await { Ok(stream) => { let (read, mut write) = stream.into_split(); diff --git a/hive-c0re/src/agent_server.rs b/hive-c0re/src/agent_server.rs index 92401386..4e68a2c7 100644 --- a/hive-c0re/src/agent_server.rs +++ b/hive-c0re/src/agent_server.rs @@ -301,6 +301,7 @@ pub(crate) async fn dispatch_shared( }) } +#[allow(clippy::too_many_lines)] async fn dispatch(req: &AgentRequest, agent: &str, coord: &Arc) -> AgentResponse { if let Some(resp) = dispatch_shared(req, agent, coord).await { return resp; @@ -497,8 +498,7 @@ async fn dispatch(req: &AgentRequest, agent: &str, coord: &Arc) -> }; } tracing::info!(%agent, %name, "agent: request_init_config for child"); - match crate::manager_server::submit_init_config(coord, name, description.clone()).await - { + match crate::manager_server::submit_init_config(coord, name, description.clone()) { Ok(_id) => AgentResponse::Ok, Err(e) => AgentResponse::Err { message: format!("{e:#}"), @@ -591,7 +591,7 @@ pub async fn dispatch_host_journal( .await { Ok((stdout, stderr)) => { - let content = if !stdout.is_empty() { stdout } else { stderr }; + let content = if stdout.is_empty() { stderr } else { stdout }; AgentResponse::HostJournal { content } } Err(e) => AgentResponse::Err { diff --git a/hive-c0re/src/auto_update.rs b/hive-c0re/src/auto_update.rs index 355499ef..a265ddce 100644 --- a/hive-c0re/src/auto_update.rs +++ b/hive-c0re/src/auto_update.rs @@ -210,7 +210,7 @@ pub fn topology_sort( let mut queue: VecDeque = VecDeque::new(); // Seed roots: entries with no parent, or names not present in topo at all. for name in &name_set { - if topo.get(name).is_none_or(|p| p.is_none()) { + if topo.get(name).is_none_or(Option::is_none) { depth.insert(name.clone(), 0); queue.push_back(name.clone()); } diff --git a/hive-c0re/src/bash_tasks_vacuum.rs b/hive-c0re/src/bash_tasks_vacuum.rs index ae0975af..5e4a971e 100644 --- a/hive-c0re/src/bash_tasks_vacuum.rs +++ b/hive-c0re/src/bash_tasks_vacuum.rs @@ -23,7 +23,7 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use crate::coordinator::Coordinator; -const VACUUM_INTERVAL: Duration = Duration::from_secs(3600); +const VACUUM_INTERVAL: Duration = Duration::from_hours(1); /// Keep completed task files for 48 hours before sweeping them. const KEEP_SECS: i64 = 48 * 3600; @@ -101,7 +101,7 @@ fn should_delete(json_path: &Path, cutoff: i64) -> bool { } let completed_at = v .get("completed_at") - .and_then(|t| t.as_i64()) + .and_then(serde_json::Value::as_i64) .unwrap_or(i64::MAX); completed_at < cutoff } diff --git a/hive-c0re/src/bin/hivectl.rs b/hive-c0re/src/bin/hivectl.rs index ac42d0ee..06f8208a 100644 --- a/hive-c0re/src/bin/hivectl.rs +++ b/hive-c0re/src/bin/hivectl.rs @@ -60,7 +60,7 @@ enum Cmd { /// Gateway htpasswd user management. Add, remove, or list users in /// an htpasswd file used by the gateway's HTTP Basic auth /// (`services.hyperhive.gateway.auth`). Credentials are stored as - /// BCrypt hashes — no extra service or PAM required. + /// `BCrypt` hashes — no extra service or PAM required. Gateway { #[command(subcommand)] cmd: GatewayCmd, @@ -179,7 +179,7 @@ enum MatrixCmd { SyncAdmin, /// Promote a matrix user to homeserver admin via the admin API. /// Uses the hive system admin token at - /// `/var/lib/hyperhive/matrix-admin-token`. The server_name is + /// `/var/lib/hyperhive/matrix-admin-token`. The `server_name` is /// discovered automatically from the running homeserver. PromoteUser { /// Matrix localpart of the user to promote (e.g. `argus`). @@ -205,7 +205,7 @@ const DEFAULT_HTPASSWD_FILE: &str = "/var/lib/hyperhive/gateway/gateway.htpasswd #[derive(Subcommand)] enum GatewayCmd { /// Add a new user or update the password of an existing user in the - /// gateway htpasswd file. The password is hashed with BCrypt (cost 12). + /// gateway htpasswd file. The password is hashed with `BCrypt` (cost 12). /// /// Pass `--password-stdin` when scripting or when you don't want the /// password visible in shell history. The file is created if it does @@ -581,7 +581,7 @@ fn htpasswd_write(path: &Path, lines: &[String]) -> Result<()> { } /// Add or update `username` in the htpasswd file at `file`, hashing -/// `password` with BCrypt (cost 12). Creates the file when absent. +/// `password` with `BCrypt` (cost 12). Creates the file when absent. fn gateway_create_user( file: &Path, username: &str, @@ -698,7 +698,7 @@ fn validate_htpasswd_username(username: &str) -> Result<()> { if username.contains(':') { bail!("username must not contain ':' (htpasswd field separator)"); } - if username.chars().any(|c| c.is_control()) { + if username.chars().any(char::is_control) { bail!("username must not contain control characters"); } Ok(()) diff --git a/hive-c0re/src/container_view.rs b/hive-c0re/src/container_view.rs index 841f9f68..cf01d0bc 100644 --- a/hive-c0re/src/container_view.rs +++ b/hive-c0re/src/container_view.rs @@ -118,11 +118,11 @@ fn read_harness_flags(name: &str) -> (bool, bool) { { let rl = v .get("rate_limited") - .and_then(|x| x.as_bool()) + .and_then(serde_json::Value::as_bool) .unwrap_or(false); let nl = v .get("needs_login") - .and_then(|x| x.as_bool()) + .and_then(serde_json::Value::as_bool) .unwrap_or(false); return (rl, nl); } diff --git a/hive-c0re/src/coordinator.rs b/hive-c0re/src/coordinator.rs index 8efd4d50..c9846c9a 100644 --- a/hive-c0re/src/coordinator.rs +++ b/hive-c0re/src/coordinator.rs @@ -433,7 +433,7 @@ impl Coordinator { } /// Emit a `ToolGroupsChanged` snapshot event. Called from the - /// rebuild-queue worker after a `PermChange` / ToolGroups entry + /// rebuild-queue worker after a `PermChange` / `ToolGroups` entry /// commits the JSON file, so the P3RM1SS10NS tab updates live. pub fn emit_tool_groups_snapshot(self: &Arc) { use hive_sh4re::ToolGroup; diff --git a/hive-c0re/src/dashboard.rs b/hive-c0re/src/dashboard.rs index c03e4ef2..6082ead7 100644 --- a/hive-c0re/src/dashboard.rs +++ b/hive-c0re/src/dashboard.rs @@ -1432,8 +1432,7 @@ mod tests { fn tmproot(tag: &str) -> std::path::PathBuf { let ts = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_nanos()) - .unwrap_or(0); + .map_or(0, |d| d.as_nanos()); let p = std::env::temp_dir().join(format!("hyperhive-test-{tag}-{ts}")); std::fs::create_dir_all(&p).unwrap(); p diff --git a/hive-c0re/src/dashboard_events.rs b/hive-c0re/src/dashboard_events.rs index 52bbf3c2..aaaefbbc 100644 --- a/hive-c0re/src/dashboard_events.rs +++ b/hive-c0re/src/dashboard_events.rs @@ -415,7 +415,7 @@ mod tests { .get("kind") .and_then(|k| k.as_str()) .expect("kind field present"); - assert_eq!(ev.kind_tag(), serde_kind, "kind_tag() drift on {ev:?}",); + assert_eq!(ev.kind_tag(), serde_kind, "kind_tag() drift on {ev:?}"); } } } diff --git a/hive-c0re/src/gateway_nginx.rs b/hive-c0re/src/gateway_nginx.rs index 70468784..e84f7a22 100644 --- a/hive-c0re/src/gateway_nginx.rs +++ b/hive-c0re/src/gateway_nginx.rs @@ -228,9 +228,9 @@ pub async fn write(names: &[String]) -> Result<()> { /// (gateway container temporarily down, systemd-run error) is /// recovered automatically without requiring a new file write. /// -/// Backs off to one retry per [`RELOAD_RETRY_SECS`] after a failure so a +/// Backs off to one retry per `RELOAD_RETRY_SECS` after a failure so a /// permanently broken gateway doesn't hammer `systemctl` on every tick. -/// A fresh `write()` call always resets the backoff (new RELOAD_PENDING +/// A fresh `write()` call always resets the backoff (new `RELOAD_PENDING` /// set to `true` + immediate attempt) so topology changes are still /// applied promptly. pub async fn reload_if_pending() { diff --git a/hive-c0re/src/knowledge.rs b/hive-c0re/src/knowledge.rs index 6ec41a45..a99be93b 100644 --- a/hive-c0re/src/knowledge.rs +++ b/hive-c0re/src/knowledge.rs @@ -25,7 +25,7 @@ pub const LOCAL_DIR: &str = "/var/lib/hyperhive/knowledge"; pub const CONTAINER_MOUNT: &str = "/knowledge"; /// Default README pushed to a freshly created `internal/knowledge` repo. -/// Short explanation + empty ToC with an HTML comment instructing contributors +/// Short explanation + empty table-of-contents with an HTML comment instructing contributors /// to add entries when they create new files. const README_CONTENT: &str = "\ # knowledge @@ -114,10 +114,10 @@ async fn seed_readme(core_token: &str) -> Result<()> { .args(["-C", LOCAL_DIR].iter().chain(args.iter())) .output() .await - .with_context(|| format!("git {:?}", args))?; + .with_context(|| format!("git {args:?}"))?; if !out.status.success() { let stderr = String::from_utf8_lossy(&out.stderr).trim().to_owned(); - anyhow::bail!("git {:?} failed: {stderr}", args); + anyhow::bail!("git {args:?} failed: {stderr}"); } } let url = format!("http://core:{core_token}@localhost:3000/{ORG}/{REPO}.git"); diff --git a/hive-c0re/src/lifecycle.rs b/hive-c0re/src/lifecycle.rs index b8e00982..ba9a57dc 100644 --- a/hive-c0re/src/lifecycle.rs +++ b/hive-c0re/src/lifecycle.rs @@ -1127,6 +1127,7 @@ fn bind_child_agent_dirs(child: &str, binds: &mut Vec) { }); } +#[allow(clippy::too_many_lines)] async fn set_nspawn_flags( container: &str, runtime_dir: &Path, @@ -1299,7 +1300,7 @@ async fn set_nspawn_flags( /// Build the per-line callback for `create_container_streaming` / /// `update_container_streaming`. Both ops share identical dispatch logic -/// (stdout → info + append_stdout, stderr → warn + append_stderr); this +/// (stdout → info + `append_stdout`, stderr → warn + `append_stderr`); this /// helper avoids duplicating that match body across the two call sites. fn make_log_callback( logs: Option>, @@ -1324,7 +1325,7 @@ fn make_log_callback( } /// Execute a container operation via hive-priv and integrate with -/// build_logs.sqlite. hive-priv runs as root and forwards output lines +/// `build_logs.sqlite`. hive-priv runs as root and forwards output lines /// to hive-c0re in real time via the streaming priv protocol. Each line /// is appended to the build-log row as it arrives, so the dashboard /// shows live progress during long `nixos-container create` / `update` runs. diff --git a/hive-c0re/src/main.rs b/hive-c0re/src/main.rs index 3cfb814f..72f1520d 100644 --- a/hive-c0re/src/main.rs +++ b/hive-c0re/src/main.rs @@ -206,6 +206,7 @@ async fn main() -> Result<()> { /// dashboard), then serve the admin socket until a signal arrives. #[allow( clippy::too_many_arguments, + clippy::too_many_lines, reason = "the `serve` subcommand's args are the host-level config the daemon \ boots from (flakes, ports, pronouns, context-window + resource \ limits); they flow straight through to Coordinator::open" diff --git a/hive-c0re/src/manager_server.rs b/hive-c0re/src/manager_server.rs index 27a31d4d..a8d5158b 100644 --- a/hive-c0re/src/manager_server.rs +++ b/hive-c0re/src/manager_server.rs @@ -83,7 +83,7 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc) -> ManagerResp match req { ManagerRequest::RequestInitConfig { name, description } => { tracing::info!(%name, "manager: request_init_config"); - match submit_init_config(coord, name, description.clone()).await { + match submit_init_config(coord, name, description.clone()) { Ok(_id) => ManagerResponse::Ok, Err(e) => ManagerResponse::Err { message: format!("{e:#}"), @@ -245,7 +245,7 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc) -> ManagerResp .await { Ok((stdout, stderr)) => { - let content = if !stdout.is_empty() { stdout } else { stderr }; + let content = if stdout.is_empty() { stderr } else { stdout }; ManagerResponse::Logs { content } } Err(e) => ManagerResponse::Err { @@ -340,7 +340,7 @@ pub(crate) fn validate_commit_ref(commit_ref: &str) -> Result<()> { /// Queue an `InitConfig` approval for a brand-new agent whose config repo /// does not yet exist. Shared between the manager and agent sockets. -pub(crate) async fn submit_init_config( +pub(crate) fn submit_init_config( coord: &Arc, name: &str, description: Option, diff --git a/hive-c0re/src/matrix.rs b/hive-c0re/src/matrix.rs index e55a8605..32397c6e 100644 --- a/hive-c0re/src/matrix.rs +++ b/hive-c0re/src/matrix.rs @@ -347,7 +347,7 @@ async fn discover_admin_room_id( } json["room_id"] .as_str() - .map(|s| s.to_owned()) + .map(ToString::to_string) .ok_or_else(|| anyhow::anyhow!("matrix: admin room alias response missing room_id: {json}")) } @@ -449,7 +449,7 @@ mod extract_new_password_tests { /// /// Strategy: send the command, capture its `event_id`, then poll backwards /// (`dir=b&limit=20`) on each tick. Events in a backward response are -/// newest-first; we walk the list until we find our own command event_id, +/// newest-first; we walk the list until we find our own command `event_id`, /// then stop — everything before that marker in the list is a response that /// arrived *after* our command. We check `body` and `formatted_body` of /// every non-self message in that window. @@ -651,31 +651,30 @@ pub async fn ensure_user_for( // Account already exists — try to re-login with the stored password. tracing::info!(%name, "matrix: user already exists, attempting login with stored password"); let pw_path = password_path(name); - let stored = match std::fs::read_to_string(&pw_path) + let stored = if let Some(pw) = std::fs::read_to_string(&pw_path) .ok() .map(|s| s.trim().to_owned()) .filter(|s| !s.is_empty()) { - Some(pw) => pw, - None => { - // Password file missing — attempt auto-recovery via admin API. - // This covers the case where agent state dirs were wiped but the - // homeserver still has the accounts. Requires the hive admin - // token at /var/lib/hyperhive/matrix-admin-token. - tracing::info!( - %name, - "matrix: stored password missing, attempting admin-API auto-recovery" - ); - match auto_reset_password(client, name).await { - Ok(new_pw) => new_pw, - Err(e) => { - anyhow::bail!( - "matrix: user {name} already exists but password is missing \ + pw + } else { + // Password file missing — attempt auto-recovery via admin API. + // This covers the case where agent state dirs were wiped but the + // homeserver still has the accounts. Requires the hive admin + // token at /var/lib/hyperhive/matrix-admin-token. + tracing::info!( + %name, + "matrix: stored password missing, attempting admin-API auto-recovery" + ); + match auto_reset_password(client, name).await { + Ok(new_pw) => new_pw, + Err(e) => { + anyhow::bail!( + "matrix: user {name} already exists but password is missing \ and admin auto-recovery failed ({e:#}) — run:\n\ hivectl matrix reset-password {name}\n\ hivectl matrix create-user {name}" - ) - } + ) } } }; @@ -783,7 +782,7 @@ pub async fn sync_agent_standalone(name: &str) { /// non-empty. Does NOT promote the account via API (that requires /// admin rights which this fn bootstraps); on a fresh homeserver the /// first-registered rule fires automatically; on an existing homeserver -/// the operator must promote the account once via `hivectl matrix +/// the operator must promote the account once via /// `hivectl matrix promote-user hive` or the conduit admin room. pub async fn ensure_admin_user(client: &reqwest::Client, register_token: &str) -> Result<()> { use std::os::unix::fs::PermissionsExt; diff --git a/hive-c0re/src/meta.rs b/hive-c0re/src/meta.rs index 8acb7142..8027b6d8 100644 --- a/hive-c0re/src/meta.rs +++ b/hive-c0re/src/meta.rs @@ -334,7 +334,7 @@ pub async fn lock_update_hyperhive() -> Result<()> { /// Write the tool-groups file for `agent` and commit it atomically /// under `META_LOCK`. Ensures the JSON change is staged + committed /// before the next `prepare_deploy` or `sync_agents` runs, so the -/// working tree is never left dirty by an untimely PermChange write. +/// working tree is never left dirty by an untimely `PermChange` write. pub async fn commit_tool_groups(agent: &str, groups: &[String]) -> Result<()> { let _guard = META_LOCK.lock().await; crate::tool_groups::set_groups(agent, groups)?; @@ -467,11 +467,7 @@ pub async fn bulk_commit_topology( .iter() .filter_map(|(child, new_parent)| { let old = topo_before.get(*child).cloned().flatten(); - if old.as_deref() != *new_parent { - Some((child.to_string(), old)) - } else { - None - } + (old.as_deref() != *new_parent).then_some((child.to_string(), old)) }) .collect(); Ok(changed) diff --git a/hive-c0re/src/migrate.rs b/hive-c0re/src/migrate.rs index d149143d..e9b92861 100644 --- a/hive-c0re/src/migrate.rs +++ b/hive-c0re/src/migrate.rs @@ -147,7 +147,7 @@ fn migrate_harness_files(name: &str) { match std::fs::rename(&src, &dst) { Ok(()) => tracing::info!(%name, %file, "migration: moved to harness dir"), Err(e) => { - tracing::warn!(%name, %file, error = ?e, "migration: move to harness dir failed") + tracing::warn!(%name, %file, error = ?e, "migration: move to harness dir failed"); } } } diff --git a/hive-c0re/src/priv_client.rs b/hive-c0re/src/priv_client.rs index 0556bfb8..9ff74cca 100644 --- a/hive-c0re/src/priv_client.rs +++ b/hive-c0re/src/priv_client.rs @@ -106,7 +106,7 @@ pub async fn update_container(name: &str) -> Result<(String, String)> { /// Streaming variant: forward stdout/stderr lines to `on_line` as they /// arrive. Returns `Ok(())` on success; the callback is responsible for -/// appending lines to build_logs or otherwise capturing the output. +/// appending lines to `build_logs` or otherwise capturing the output. pub async fn update_container_streaming( name: &str, on_line: impl FnMut(PrivStream, &str), diff --git a/hive-c0re/src/rebuild_queue.rs b/hive-c0re/src/rebuild_queue.rs index 3da40a2d..425203dd 100644 --- a/hive-c0re/src/rebuild_queue.rs +++ b/hive-c0re/src/rebuild_queue.rs @@ -1364,9 +1364,9 @@ mod tests { assert!(!q.set_step(999, "anything")); } - /// A MetaUpdate cascade Rebuild (with parent_id = Some(meta_id)) must - /// NOT dedup into a pre-existing Queued Rebuild with a different parent_id - /// (e.g. from a startup sweep). Without the parent_id dedup guard the + /// A `MetaUpdate` cascade `Rebuild` (with `parent_id` = `Some(meta_id)`) must + /// NOT dedup into a pre-existing `Queued` `Rebuild` with a different `parent_id` + /// (e.g. from a startup sweep). Without the `parent_id` dedup guard the /// cascade rebuild would be swallowed and the agent would never rebuild /// against the post-lock-bump meta. #[test] diff --git a/hive-c0re/src/reminder_scheduler.rs b/hive-c0re/src/reminder_scheduler.rs index df00fb8d..a78abbb5 100644 --- a/hive-c0re/src/reminder_scheduler.rs +++ b/hive-c0re/src/reminder_scheduler.rs @@ -63,7 +63,7 @@ fn tick(coord: &Arc) { // Single-transaction batch: one DB lock acquisition for N reminders // instead of N sequential lock/unlock cycles. let results = coord.broker.deliver_reminders_batch(&items); - let any_delivered = results.iter().any(|r| r.is_ok()); + let any_delivered = results.iter().any(Result::is_ok); for ((id, agent, _body), result) in items.iter().zip(results.iter()) { if let Err(e) = result { let reason = format!("{e:#}"); diff --git a/hive-c0re/src/topology.rs b/hive-c0re/src/topology.rs index a24946bd..b14eb249 100644 --- a/hive-c0re/src/topology.rs +++ b/hive-c0re/src/topology.rs @@ -666,7 +666,7 @@ mod tests { let mut top = top_level_agents_in(&topo); top.sort(); let mut expected = vec![crate::lifecycle::MANAGER_NAME, "orphan"]; - expected.sort(); + expected.sort_unstable(); assert_eq!(top, expected); } diff --git a/hive-matrix-mcp/src/client.rs b/hive-matrix-mcp/src/client.rs index 1db66db4..e5ea8e0a 100644 --- a/hive-matrix-mcp/src/client.rs +++ b/hive-matrix-mcp/src/client.rs @@ -46,6 +46,12 @@ struct WhoamiResponse { /// session to call whoami). /// 3. Build the real Client with the sqlite store + `restore_session` /// using a synthetic `MatrixSession`. +/// +/// # Errors +/// +/// Returns an error if the token file is missing or empty, if the +/// `whoami` request fails, or if the matrix-sdk client fails to build +/// or restore the session. pub async fn build_and_restore( homeserver: &str, token_file: &Path, diff --git a/hive-matrix-mcp/src/wake.rs b/hive-matrix-mcp/src/wake.rs index 1c4bfff8..2fef492e 100644 --- a/hive-matrix-mcp/src/wake.rs +++ b/hive-matrix-mcp/src/wake.rs @@ -33,6 +33,11 @@ pub const WAKE_BODY_TRUNCATE: usize = 100; /// `#[serde(tag = "cmd", rename_all = "snake_case")]`. Must be `"cmd"`, /// not `"kind"` — the harness deserialises against the hive-sh4re type /// and silently discards requests that don't match. +/// +/// # Errors +/// +/// Returns an error on socket connect failure, serialisation failure, +/// or I/O error writing to or reading from the socket. pub async fn send_wake(socket: &Path, body: impl AsRef) -> Result<()> { use tokio::io::AsyncBufReadExt; @@ -117,6 +122,7 @@ pub fn format_unread_summary(rooms: &[crate::protocol::RoomUnread]) -> String { /// Truncate `s` to `max` Unicode chars, appending `…` when cut. /// Char-based not byte-based so multi-byte content (most chat) doesn't /// get cut mid-codepoint. +#[must_use] pub fn truncate_chars(s: &str, max: usize) -> String { let mut end = s.len(); for (count, (i, _)) in s.char_indices().enumerate() { diff --git a/hive-priv/src/main.rs b/hive-priv/src/main.rs index 29c79532..e46cc78e 100644 --- a/hive-priv/src/main.rs +++ b/hive-priv/src/main.rs @@ -57,6 +57,7 @@ async fn main() -> Result<()> { } fn socket_listener() -> Result { + use std::os::unix::fs::PermissionsExt as _; // Socket activation: systemd passes the socket as fd 3 when // LISTEN_FDS >= 1 and LISTEN_PID matches our pid. let listen_fds: Option = std::env::var("LISTEN_FDS") @@ -92,7 +93,6 @@ fn socket_listener() -> Result { let _ = std::fs::remove_file(path); let listener = UnixListener::bind(path).with_context(|| format!("bind {PRIV_SOCK}"))?; // Mode 0660: only the hive-core group can connect. - use std::os::unix::fs::PermissionsExt as _; std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o660)) .context("chmod priv.sock")?; tracing::info!(path = PRIV_SOCK, "bound priv socket"); @@ -163,6 +163,7 @@ async fn write_line_event(writer: &mut OwnedWriteHalf, stream: PrivStream, data: /// For streaming ops (`CreateContainer`/`UpdateContainer` with `stream: true`) /// output lines are forwarded to `writer` as `PrivEvent::Line` messages and /// the returned strings are empty. +#[allow(clippy::too_many_lines)] async fn exec(req: PrivRequest, writer: &mut OwnedWriteHalf) -> Result<(String, String)> { match req { PrivRequest::StartContainer { ref name } => { @@ -232,7 +233,15 @@ async fn exec(req: PrivRequest, writer: &mut OwnedWriteHalf) -> Result<(String, } => { validate_container_system_name(container)?; read_container_journal( - container, lines, boot, output, unit, priority, grep, since, until, + container, + lines, + boot, + output, + unit.as_deref(), + priority.as_deref(), + grep.as_deref(), + since.as_deref(), + until.as_deref(), ) .await } @@ -308,9 +317,9 @@ async fn exec(req: PrivRequest, writer: &mut OwnedWriteHalf) -> Result<(String, ref agent_name, mode, } => { + use std::os::unix::fs::PermissionsExt as _; validate_agent_name(agent_name)?; let path = socket_dir_path(agent_name); - use std::os::unix::fs::PermissionsExt as _; std::fs::set_permissions(&path, std::fs::Permissions::from_mode(mode)) .with_context(|| format!("chmod {:o} {}", mode, path.display()))?; Ok((String::new(), String::new())) @@ -597,17 +606,17 @@ async fn container_run_streaming( /// hard error — journalctl's own diagnostic (folded into `stderr` with /// the exit status) is what the caller surfaces to the operator, so the /// helper never bails. -#[allow(clippy::too_many_arguments)] +#[allow(clippy::too_many_arguments, clippy::too_many_lines)] async fn read_container_journal( container: &str, lines: u32, boot: bool, output: JournalOutput, - unit: &Option, - priority: &Option, - grep: &Option, - since: &Option, - until: &Option, + unit: Option<&str>, + priority: Option<&str>, + grep: Option<&str>, + since: Option<&str>, + until: Option<&str>, ) -> Result<(String, String)> { let mut args: Vec = vec![ "-M".to_owned(), @@ -622,11 +631,11 @@ async fn read_container_journal( } if let Some(u) = unit { args.push("-u".to_owned()); - args.push(u.clone()); + args.push(u.to_owned()); } if let Some(p) = priority { args.push("-p".to_owned()); - args.push(p.clone()); + args.push(p.to_owned()); } // `--grep=`/`--since=`/`--until=` use the `=`-joined form so a value // can never be parsed as a separate journalctl flag. @@ -833,6 +842,7 @@ fn write_nspawn_flags( binds: &[BindMount], isolation: Option<&NetworkIsolation>, ) -> Result<()> { + use std::fmt::Write as _; let path = format!("/etc/nixos-containers/{container}.conf"); let original = std::fs::read_to_string(&path).with_context(|| format!("read {path}"))?; let lines: Vec<&str> = original @@ -855,10 +865,10 @@ fn write_nspawn_flags( if let Some(iso) = isolation { out.push_str("PRIVATE_NETWORK=1\n"); out.push_str("HOST_ADDRESS=\n"); - out.push_str(&format!("LOCAL_ADDRESS={}\n", iso.agent_ip)); + let _ = writeln!(out, "LOCAL_ADDRESS={}", iso.agent_ip); out.push_str("HOST_ADDRESS6=\n"); out.push_str("LOCAL_ADDRESS6=\n"); - out.push_str(&format!("HOST_BRIDGE={}\n", iso.bridge)); + let _ = writeln!(out, "HOST_BRIDGE={}", iso.bridge); } else { out.push_str("PRIVATE_NETWORK=0\n"); out.push_str("HOST_ADDRESS=\n"); @@ -875,6 +885,6 @@ fn write_nspawn_flags( }) .collect(); let flags_joined = flags.join(" "); - out.push_str(&format!("EXTRA_NSPAWN_FLAGS=\"{flags_joined}\"\n")); + let _ = writeln!(out, "EXTRA_NSPAWN_FLAGS=\"{flags_joined}\""); std::fs::write(&path, out).with_context(|| format!("write {path}")) } diff --git a/hive-sh4re/src/priv_proto.rs b/hive-sh4re/src/priv_proto.rs index 39ccc4f9..9fa39b6a 100644 --- a/hive-sh4re/src/priv_proto.rs +++ b/hive-sh4re/src/priv_proto.rs @@ -43,6 +43,7 @@ pub enum JournalOutput { impl JournalOutput { /// The string journalctl expects after `--output=`. + #[must_use] pub fn as_journalctl(self) -> &'static str { match self { JournalOutput::Short => "short",